diff --git a/contract/bridge/contracts/Collateral.sol b/contract/bridge/contracts/Collateral.sol index 8a9afd2..57ba7f2 100644 --- a/contract/bridge/contracts/Collateral.sol +++ b/contract/bridge/contracts/Collateral.sol @@ -4,7 +4,7 @@ pragma solidity 0.6.12; import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; -abstract contract ICollateral { +contract ICollateral { using SafeMathUpgradeable for uint256; event LockCollateral(address sender, uint256 amount); @@ -18,8 +18,8 @@ abstract contract ICollateral { return address(this).balance; } - function lockCollateral(address sender, uint256 amount) internal { - require(msg.value >= amount, "Invalid collateral"); + function _lockCollateral(address sender, uint256 amount) internal virtual { + require(amount > 0, "Invalid collateral"); CollateralBalances[sender] = CollateralBalances[sender].add(amount); emit LockCollateral(sender, amount); } @@ -39,21 +39,21 @@ abstract contract ICollateral { require(sent, "Transfer failed."); } - function releaseCollateral(address sender, uint256 amount) internal { + function _releaseCollateral(address sender, uint256 amount) internal virtual { release(sender, sender, amount); emit ReleaseCollateral(sender, amount); } - function slashCollateral( + function _slashCollateral( address from, address to, uint256 amount - ) internal { + ) internal virtual { release(from, to, amount); emit SlashCollateral(from, to, amount); } - function getFreeCollateral(address vaultId) + function _getFreeCollateral(address vaultId) internal view returns (uint256) @@ -65,7 +65,7 @@ abstract contract ICollateral { return CollateralBalances[vaultId]; } - function useCollateralInc(address vaultId, uint256 amount) internal { + function _useCollateralInc(address vaultId, uint256 amount) internal virtual { CollateralUsed[vaultId] = CollateralUsed[vaultId].add(amount); require( CollateralBalances[vaultId] >= CollateralUsed[vaultId], @@ -73,7 +73,7 @@ abstract contract ICollateral { ); } - function useCollateralDec(address vaultId, uint256 amount) internal { + function _useCollateralDec(address vaultId, uint256 amount) internal virtual { require(CollateralUsed[vaultId] >= amount, "Insufficient collateral"); CollateralUsed[vaultId] = CollateralUsed[vaultId].sub(amount); } diff --git a/contract/bridge/contracts/ExchangeRateOracleV2.sol b/contract/bridge/contracts/ExchangeRateOracleV2.sol index 4faf204..49aa034 100644 --- a/contract/bridge/contracts/ExchangeRateOracleV2.sol +++ b/contract/bridge/contracts/ExchangeRateOracleV2.sol @@ -1,9 +1,7 @@ /** SPDX-License-Identifier: MIT - Exchange Rate Oracle Module https://onebtc-dev.web.app/spec/oracle.html - The Exchange Rate Oracle receives a continuous data feed on the exchange rate between BTC and ONE. */ diff --git a/contract/bridge/contracts/IVaultRegistry.sol b/contract/bridge/contracts/IVaultRegistry.sol new file mode 100644 index 0000000..9837d85 --- /dev/null +++ b/contract/bridge/contracts/IVaultRegistry.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IVaultRegistry { + // // set functions for Vault + // function setBtcPublicKeyX(address, uint256) external; + // function setBtcPublicKeyY(address, uint256) external; + // function setCollateral(address, uint256) external; + // function setIssued(address, uint256) external; + // function setToBeIssued(address, uint256) external; + // function setToBeRedeemed(address, uint256) external; + // function setReplaceCollateral(address, uint256) external; + // function setToBeReplaced(address, uint256) external; + // function setLiquidatedCollateral(address, uint256) external; + + // // set function for vaultDepositAddress + // function setVaultDepositAddress(address, address, bool) external; + + // interfaces used by VaultRegistry contract + function vaults(address) external returns(uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256); + function vaultDepositAddress(address, address) external returns(bool); + function registerVault(uint256, uint256) external; + function registerDepositAddress(address, uint256) external returns(address); + function insertVaultDepositAddress(address, uint256, uint256, uint256) external returns(address); + function updatePublicKey(uint256, uint256) external; + function decreaseToBeIssuedTokens(address, uint256) external; + function tryIncreaseToBeIssuedTokens(address, uint256) external returns(bool); + function tryIncreaseToBeRedeemedTokens(address, uint256) external returns(bool); + function redeemTokens(address, uint256) external; + function issuableTokens(address) external view returns(uint256); + function issueTokens(address, uint256) external; + function calculateCollateral(uint256, uint256, uint256) external view returns(uint256); + function requestableToBeReplacedTokens(address) external returns(uint256); + function tryIncreaseToBeReplacedTokens(address, uint256, uint256) external returns(uint256, uint256); + function decreaseToBeReplacedTokens(address, uint256) external returns(uint256, uint256); + function replaceTokens(address, address, uint256, uint256) external; + function tryDepositCollateral(address, uint256) external; + function liquidateVaul(address, address) external; + function lockCollateral(address, uint256) external; + function releaseCollateral(address, uint256) external; + function slashCollateral(address, address, uint256) external; + function useCollateralInc(address, uint256) external; + function useCollateralDec(address, uint256) external; + + // interface used by StakedRelayer contract + function liquidateTheftVault(address vaultId, address reporterId) external; +} diff --git a/contract/bridge/contracts/Issue.sol b/contract/bridge/contracts/Issue.sol index f9f3a11..907c45e 100644 --- a/contract/bridge/contracts/Issue.sol +++ b/contract/bridge/contracts/Issue.sol @@ -7,10 +7,9 @@ import "@interlay/bitcoin-spv-sol/contracts/BTCUtils.sol"; import "@interlay/bitcoin-spv-sol/contracts/BytesLib.sol"; import "./Request.sol"; import "./TxValidate.sol"; -import "./Collateral.sol"; -import "./VaultRegistry.sol"; +import "./IVaultRegistry.sol"; -abstract contract Issue is VaultRegistry, Request { +abstract contract Issue is Request { using BTCUtils for bytes; using BytesLib for bytes; using SafeMathUpgradeable for uint256; @@ -71,6 +70,7 @@ abstract contract Issue is VaultRegistry, Request { function getIssueGriefingCollateral(uint256 amountBtc) private + pure returns (uint256) { return amountBtc; @@ -93,6 +93,7 @@ abstract contract Issue is VaultRegistry, Request { } function _requestIssue( + IVaultRegistry vaultRegistry, address payable requester, uint256 amountRequested, address vaultId, @@ -103,11 +104,11 @@ abstract contract Issue is VaultRegistry, Request { "Insufficient collateral" ); require( - VaultRegistry.tryIncreaseToBeIssuedTokens(vaultId, amountRequested), + vaultRegistry.tryIncreaseToBeIssuedTokens(vaultId, amountRequested), "Amount requested exceeds vault limit" ); uint256 issueId = getIssueId(requester); - address btcAddress = VaultRegistry.registerDepositAddress( + address btcAddress = vaultRegistry.registerDepositAddress( vaultId, issueId ); @@ -127,7 +128,7 @@ abstract contract Issue is VaultRegistry, Request { request.btcHeight = 0; request.status = RequestStatus.Pending; } - ICollateral.lockCollateral( + vaultRegistry.lockCollateral( request.requester, request.griefingCollateral ); @@ -142,6 +143,7 @@ abstract contract Issue is VaultRegistry, Request { } function _executeIssue( + IVaultRegistry vaultRegistry, address requester, uint256 issueId, bytes memory _vout, @@ -164,20 +166,20 @@ abstract contract Issue is VaultRegistry, Request { // only the requester of the issue can execute payments with different amounts require(msg.sender == request.requester, "Invalid executor"); uint256 deficit = expectedTotalAmount - amountTransferred; - VaultRegistry.decreaseToBeIssuedTokens(request.vault, deficit); - uint256 releasedCollateral = VaultRegistry.calculateCollateral( + vaultRegistry.decreaseToBeIssuedTokens(request.vault, deficit); + uint256 releasedCollateral = vaultRegistry.calculateCollateral( request.griefingCollateral, amountTransferred, expectedTotalAmount ); - ICollateral.releaseCollateral( + vaultRegistry.releaseCollateral( request.requester, releasedCollateral ); uint256 slashedCollateral = request.griefingCollateral.sub( releasedCollateral ); - ICollateral.slashCollateral( + vaultRegistry.slashCollateral( request.requester, request.vault, slashedCollateral @@ -189,14 +191,14 @@ abstract contract Issue is VaultRegistry, Request { slashedCollateral ); } else { - ICollateral.releaseCollateral( + vaultRegistry.releaseCollateral( request.requester, request.griefingCollateral ); if (amountTransferred > expectedTotalAmount) { uint256 surplusBtc = amountTransferred.sub(expectedTotalAmount); if ( - VaultRegistry.tryIncreaseToBeIssuedTokens( + vaultRegistry.tryIncreaseToBeIssuedTokens( request.vault, surplusBtc ) @@ -210,7 +212,7 @@ abstract contract Issue is VaultRegistry, Request { } } uint256 total = request.amount.add(request.fee); - VaultRegistry.issueTokens(request.vault, total); + vaultRegistry.issueTokens(request.vault, total); issueOneBTC(request.vault, request.fee); issueOneBTC(request.requester, request.amount); request.status = RequestStatus.Completed; @@ -226,7 +228,7 @@ abstract contract Issue is VaultRegistry, Request { ); } - function _cancelIssue(address requester, uint256 issueId) internal { + function _cancelIssue(IVaultRegistry vaultRegistry, address requester, uint256 issueId) internal { IssueRequest storage request = issueRequests[requester][issueId]; require( request.status == RequestStatus.Pending, @@ -237,12 +239,12 @@ abstract contract Issue is VaultRegistry, Request { "Time not expired" ); request.status = RequestStatus.Cancelled; - ICollateral.slashCollateral( + vaultRegistry.slashCollateral( request.requester, request.vault, request.griefingCollateral ); - VaultRegistry.decreaseToBeIssuedTokens( + vaultRegistry.decreaseToBeIssuedTokens( request.vault, request.amount + request.fee ); diff --git a/contract/bridge/contracts/OneBtc.sol b/contract/bridge/contracts/OneBtc.sol index 77960ac..5e05ef9 100644 --- a/contract/bridge/contracts/OneBtc.sol +++ b/contract/bridge/contracts/OneBtc.sol @@ -2,6 +2,7 @@ pragma solidity 0.6.12; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import {ValidateSPV} from "@interlay/bitcoin-spv-sol/contracts/ValidateSPV.sol"; import {TransactionUtils} from "./TransactionUtils.sol"; @@ -10,15 +11,24 @@ import {Redeem} from "./Redeem.sol"; import {Replace} from "./Replace.sol"; import {IRelay} from "./IRelay.sol"; import "./IExchangeRateOracle.sol"; +import "./IVaultRegistry.sol"; + +contract OneBtc is OwnableUpgradeable, ERC20Upgradeable, Issue, Redeem, Replace { + event UpdateRelayAddress(address indexed by, address indexed oldRelay, address indexed newRelay); + event UpdateOracleAddress(address indexed by, address indexed oldOracle, address indexed newOracle); + event UpdateVaultRegistryAddress(address indexed by, address oldVaultRegistry, address indexed newVaultRegistry); -contract OneBtc is ERC20Upgradeable, Issue, Redeem, Replace { IRelay public relay; + IVaultRegistry public vaultRegistry; + IExchangeRateOracle oracle; - function initialize(IRelay _relay, IExchangeRateOracle _oracle) external initializer { + function initialize(IRelay _relay, IExchangeRateOracle _oracle, IVaultRegistry _vaultRegistry) external initializer { + __Ownable_init(); __ERC20_init("Harmony Bitcoin", "1BTC"); _setupDecimals(8); relay = _relay; oracle = _oracle; + vaultRegistry = _vaultRegistry; } function verifyTx( @@ -27,7 +37,7 @@ contract OneBtc is ERC20Upgradeable, Issue, Redeem, Replace { bytes calldata rawTx, bytes calldata header, bytes calldata merkleProof - ) public returns (bytes memory) { + ) public view returns (bytes memory) { bytes32 txId = rawTx.hash256(); relay.verifyTx( height, @@ -38,8 +48,7 @@ contract OneBtc is ERC20Upgradeable, Issue, Redeem, Replace { 1, true ); - TransactionUtils.Transaction memory btcTx = - TransactionUtils.extractTx(rawTx); + TransactionUtils.Transaction memory btcTx = TransactionUtils.extractTx(rawTx); require(btcTx.locktime == 0, "Locktime must be zero"); // check version? // btcTx.version @@ -50,7 +59,7 @@ contract OneBtc is ERC20Upgradeable, Issue, Redeem, Replace { external payable { - Issue._requestIssue(msg.sender, amountRequested, vaultId, msg.value); + Issue._requestIssue(vaultRegistry, msg.sender, amountRequested, vaultId, msg.value); } function executeIssue( @@ -71,11 +80,11 @@ contract OneBtc is ERC20Upgradeable, Issue, Redeem, Replace { merkleProof ); - Issue._executeIssue(requester, issueId, _vout, outputIndex); + Issue._executeIssue(vaultRegistry, requester, issueId, _vout, outputIndex); } function cancelIssue(address requester, uint256 issueId) external { - Issue._cancelIssue(requester, issueId); + Issue._cancelIssue(vaultRegistry, requester, issueId); } function requestRedeem( @@ -83,7 +92,7 @@ contract OneBtc is ERC20Upgradeable, Issue, Redeem, Replace { address btcAddress, address vaultId ) external { - Redeem._requestRedeem(msg.sender, amountOneBtc, btcAddress, vaultId); + Redeem._requestRedeem(vaultRegistry, msg.sender, amountOneBtc, btcAddress, vaultId); } function executeRedeem( @@ -98,18 +107,18 @@ contract OneBtc is ERC20Upgradeable, Issue, Redeem, Replace { bytes memory _vout = verifyTx(height, index, rawTx, header, merkleProof); - Redeem._executeRedeem(requester, redeemId, _vout); + Redeem._executeRedeem(vaultRegistry, requester, redeemId, _vout); } function cancelRedeem(address requester, uint256 redeemId) external { - Redeem._cancelRedeem(requester, redeemId); + Redeem._cancelRedeem(vaultRegistry, requester, redeemId); } function lockOneBTC(address from, uint256 amount) internal override(Redeem) { - ERC20Upgradeable._transfer(msg.sender, address(this), amount); + ERC20Upgradeable._transfer(from, address(this), amount); } function burnLockedOneBTC(uint256 amount) internal override(Redeem) { @@ -135,7 +144,7 @@ contract OneBtc is ERC20Upgradeable, Issue, Redeem, Replace { uint256 btcAmount, uint256 griefingCollateral ) external payable { - Replace._requestReplace(oldVaultId, btcAmount, griefingCollateral); + Replace._requestReplace(vaultRegistry, oldVaultId, btcAmount, griefingCollateral); } function acceptReplace( @@ -147,6 +156,7 @@ contract OneBtc is ERC20Upgradeable, Issue, Redeem, Replace { uint256 btcPublicKeyY ) external payable { Replace._acceptReplace( + vaultRegistry, oldVaultId, newVaultId, btcAmount, @@ -166,6 +176,27 @@ contract OneBtc is ERC20Upgradeable, Issue, Redeem, Replace { bytes calldata header ) external { bytes memory _vout = verifyTx(height, index, rawTx, header, merkleProof); - Replace._executeReplace(replaceId, _vout); + Replace._executeReplace(vaultRegistry, replaceId, _vout); + } + + function updateRelayAddress(IRelay _relay) external onlyOwner { + UpdateRelayAddress(msg.sender, address(relay), address(_relay)); + + require(address(_relay) != address(0), "Zero address for Relay"); + relay = _relay; + } + + function updateOracleAddress(IExchangeRateOracle _oracle) external onlyOwner { + UpdateOracleAddress(msg.sender, address(oracle), address(_oracle)); + + require(address(_oracle) != address(0), "Zero address for Oracle"); + oracle = _oracle; + } + + function updateVaultRegistryAddress(IVaultRegistry _vaultRegistry) external onlyOwner { + UpdateVaultRegistryAddress(msg.sender, address(vaultRegistry), address(_vaultRegistry)); + + require(address(_vaultRegistry) != address(0), "Zero addess for VaultRegistry"); + vaultRegistry = _vaultRegistry; } } diff --git a/contract/bridge/contracts/Redeem.sol b/contract/bridge/contracts/Redeem.sol index 81325c9..7fcc3f0 100644 --- a/contract/bridge/contracts/Redeem.sol +++ b/contract/bridge/contracts/Redeem.sol @@ -6,10 +6,9 @@ import {BTCUtils} from "@interlay/bitcoin-spv-sol/contracts/BTCUtils.sol"; import {BytesLib} from "@interlay/bitcoin-spv-sol/contracts/BytesLib.sol"; import {Request} from "./Request.sol"; import {TxValidate} from "./TxValidate.sol"; -import {ICollateral} from "./Collateral.sol"; -import {VaultRegistry} from "./VaultRegistry.sol"; +import "./IVaultRegistry.sol"; -abstract contract Redeem is VaultRegistry, Request { +abstract contract Redeem is Request { using BTCUtils for bytes; using BytesLib for bytes; @@ -64,15 +63,16 @@ abstract contract Redeem is VaultRegistry, Request { ); } - function getRedeemCollateral(uint256 amountBtc) private returns (uint256) { + function getRedeemCollateral(uint256 amountBtc) private pure returns (uint256) { return amountBtc; } - function getCurrentInclusionFee() private returns (uint256) { + function getCurrentInclusionFee() private pure returns (uint256) { return 0; } function _requestRedeem( + IVaultRegistry vaultRegistry, address requester, uint256 amountOneBtc, address btcAddress, @@ -86,7 +86,7 @@ abstract contract Redeem is VaultRegistry, Request { uint256 redeemId = getRedeemId(requester); require( - VaultRegistry.tryIncreaseToBeRedeemedTokens(vaultId, toBeBurnedBtc), + vaultRegistry.tryIncreaseToBeRedeemedTokens(vaultId, toBeBurnedBtc), "Insufficient tokens committed" ); // TODO: decrease collateral @@ -106,7 +106,7 @@ abstract contract Redeem is VaultRegistry, Request { //request.btcHeight request.status = RequestStatus.Pending; } - ICollateral.useCollateralInc(vaultId, request.amountOne); + vaultRegistry.useCollateralInc(vaultId, request.amountOne); emit RedeemRequested( redeemId, requester, @@ -118,6 +118,7 @@ abstract contract Redeem is VaultRegistry, Request { } function _executeRedeem( + IVaultRegistry vaultRegistry, address requester, uint256 redeemId, bytes memory _vout @@ -137,8 +138,8 @@ abstract contract Redeem is VaultRegistry, Request { burnLockedOneBTC(request.amountBtc); releaseLockedOneBTC(request.vault, request.fee); request.status = RequestStatus.Completed; - ICollateral.useCollateralDec(request.vault, request.amountOne); - VaultRegistry.redeemTokens( + vaultRegistry.useCollateralDec(request.vault, request.amountOne); + vaultRegistry.redeemTokens( request.vault, request.amountBtc + request.transferFeeBtc ); @@ -152,7 +153,7 @@ abstract contract Redeem is VaultRegistry, Request { ); } - function _cancelRedeem(address requester, uint256 redeemId) internal { + function _cancelRedeem(IVaultRegistry vaultRegistry, address requester, uint256 redeemId) internal { RedeemRequest storage request = redeemRequests[requester][redeemId]; require( request.status == RequestStatus.Pending, @@ -165,8 +166,8 @@ abstract contract Redeem is VaultRegistry, Request { request.status = RequestStatus.Cancelled; releaseLockedOneBTC(request.requester, request.amountBtc + request.fee); - ICollateral.useCollateralDec(request.vault, request.amountOne); - ICollateral.slashCollateral( + vaultRegistry.useCollateralDec(request.vault, request.amountOne); + vaultRegistry.slashCollateral( request.vault, request.requester, request.amountOne diff --git a/contract/bridge/contracts/Replace.sol b/contract/bridge/contracts/Replace.sol index ba1426a..2d8e755 100644 --- a/contract/bridge/contracts/Replace.sol +++ b/contract/bridge/contracts/Replace.sol @@ -7,10 +7,9 @@ import {BTCUtils} from "@interlay/bitcoin-spv-sol/contracts/BTCUtils.sol"; import {BytesLib} from "@interlay/bitcoin-spv-sol/contracts/BytesLib.sol"; import {Request} from "./Request.sol"; import {TxValidate} from "./TxValidate.sol"; -import {ICollateral} from "./Collateral.sol"; -import {VaultRegistry} from "./VaultRegistry.sol"; +import "./IVaultRegistry.sol"; -abstract contract Replace is VaultRegistry, Request { +abstract contract Replace is Request { // using BTCUtils for bytes; // using BytesLib for bytes; @@ -58,13 +57,14 @@ abstract contract Replace is VaultRegistry, Request { ); } - function getReplaceBtcDustValue() private returns (uint256) { + function getReplaceBtcDustValue() private pure returns (uint256) { // TODO return 0; } function getReplaceGriefingCollateral(uint256 amountBtc) private + pure returns (uint256) { // TODO @@ -72,6 +72,7 @@ abstract contract Replace is VaultRegistry, Request { } function _requestReplace( + IVaultRegistry vaultRegistry, address payable oldVaultId, uint256 btcAmount, uint256 griefingCollateral @@ -83,7 +84,7 @@ abstract contract Replace is VaultRegistry, Request { // TODO: SECURITY CHECK (The oldVault MUST NOT be banned) - uint256 requestableTokens = VaultRegistry.requestableToBeReplacedTokens( + uint256 requestableTokens = vaultRegistry.requestableToBeReplacedTokens( oldVaultId ); uint256 toBeReplacedIncrease = MathUpgradeable.min(requestableTokens, btcAmount); @@ -91,7 +92,7 @@ abstract contract Replace is VaultRegistry, Request { uint256 replaceCollateralIncrease = griefingCollateral; if (btcAmount > 0) { - replaceCollateralIncrease = VaultRegistry.calculateCollateral( + replaceCollateralIncrease = vaultRegistry.calculateCollateral( griefingCollateral, toBeReplacedIncrease, btcAmount @@ -101,7 +102,7 @@ abstract contract Replace is VaultRegistry, Request { ( uint256 totalToBeReplaced, uint256 totalGriefingCollateral - ) = VaultRegistry.tryIncreaseToBeReplacedTokens( + ) = vaultRegistry.tryIncreaseToBeReplacedTokens( oldVaultId, toBeReplacedIncrease, replaceCollateralIncrease @@ -121,7 +122,7 @@ abstract contract Replace is VaultRegistry, Request { ); // Lock the oldVault’s griefing collateral. Note that this directly locks the amount - ICollateral.lockCollateral(oldVaultId, replaceCollateralIncrease); + vaultRegistry.lockCollateral(oldVaultId, replaceCollateralIncrease); emit RequestReplace( oldVaultId, @@ -130,14 +131,14 @@ abstract contract Replace is VaultRegistry, Request { ); } - function _withdrawReplace(address oldVaultId, uint256 btcAmount) internal { + function _withdrawReplace(IVaultRegistry vaultRegistry, address oldVaultId, uint256 btcAmount) internal { require(msg.sender == oldVaultId, "Sender should be old vault owner"); // TODO: SECURITY CHECK (The oldVault MUST NOT be banned) - (uint256 withdrawnTokens, uint256 toWithdrawCollateral) = VaultRegistry + (uint256 withdrawnTokens, uint256 toWithdrawCollateral) = vaultRegistry .decreaseToBeReplacedTokens(oldVaultId, btcAmount); - ICollateral.releaseCollateral(oldVaultId, toWithdrawCollateral); + vaultRegistry.releaseCollateral(oldVaultId, toWithdrawCollateral); require(withdrawnTokens == 0, "Withdraw tokens is zero"); @@ -145,6 +146,7 @@ abstract contract Replace is VaultRegistry, Request { } function _acceptReplace( + IVaultRegistry vaultRegistry, address oldVaultId, address newVaultId, uint256 btcAmount, @@ -161,14 +163,14 @@ abstract contract Replace is VaultRegistry, Request { // TODO: The newVault’s free balance MUST be enough to lock collateral. // TODO: SECURITY CHECK (The oldVault, newVault MUST NOT be banned) - Vault storage oldVault = VaultRegistry.vaults[oldVaultId]; - Vault storage newVault = VaultRegistry.vaults[newVaultId]; + (uint256 oldVaultBtcPublicKeyX,,,,,,,,) = vaultRegistry.vaults(oldVaultId); + (uint256 newVaultBtcPublicKeyX,,,,,,,,) = vaultRegistry.vaults(newVaultId); - require(oldVault.btcPublicKeyX != 0, "Vault does not exist"); - require(newVault.btcPublicKeyX != 0, "Vault does not exist"); + require(oldVaultBtcPublicKeyX != 0, "Vault does not exist"); + require(newVaultBtcPublicKeyX != 0, "Vault does not exist"); // decrease old-vault's to-be-replaced tokens - (uint256 redeemableTokens, uint256 griefingCollateral) = VaultRegistry + (uint256 redeemableTokens, uint256 griefingCollateral) = vaultRegistry .decreaseToBeReplacedTokens(oldVaultId, btcAmount); // TODO: check amount_btc is above the minimum @@ -176,29 +178,29 @@ abstract contract Replace is VaultRegistry, Request { require(redeemableTokens >= dustValue, "Amount below dust amount"); // Calculate and lock the new-vault's additional collateral - uint256 actualNewVaultCollateral = VaultRegistry.calculateCollateral( + uint256 actualNewVaultCollateral = vaultRegistry.calculateCollateral( collateral, redeemableTokens, btcAmount ); - VaultRegistry.tryDepositCollateral( + vaultRegistry.tryDepositCollateral( newVaultId, actualNewVaultCollateral ); // increase old-vault's to-be-redeemed tokens - this should never fail - VaultRegistry.tryIncreaseToBeRedeemedTokens( + vaultRegistry.tryIncreaseToBeRedeemedTokens( oldVaultId, redeemableTokens ); // increase new-vault's to-be-issued tokens - this will fail if there is insufficient collateral - VaultRegistry.tryIncreaseToBeIssuedTokens(newVaultId, redeemableTokens); + vaultRegistry.tryIncreaseToBeIssuedTokens(newVaultId, redeemableTokens); uint256 replaceId = getReplaceId(oldVaultId); - address btcAddress = VaultRegistry.insertVaultDepositAddress( + address btcAddress = vaultRegistry.insertVaultDepositAddress( newVaultId, btcPublicKeyX, btcPublicKeyY, @@ -234,7 +236,7 @@ abstract contract Replace is VaultRegistry, Request { ); } - function _executeReplace(uint256 replaceId, bytes memory _vout) internal { + function _executeReplace(IVaultRegistry vaultRegistry, uint256 replaceId, bytes memory _vout) internal { // Retrieve the ReplaceRequest as per the replaceId parameter from Vaults in the VaultRegistry ReplaceRequest storage replace = replaceRequests[replaceId]; require( @@ -261,7 +263,7 @@ abstract contract Replace is VaultRegistry, Request { // decrease old-vault's issued & to-be-redeemed tokens, and // change new-vault's to-be-issued tokens to issued tokens - VaultRegistry.replaceTokens( + vaultRegistry.replaceTokens( oldVaultId, newVaultId, replace.amount, @@ -269,7 +271,7 @@ abstract contract Replace is VaultRegistry, Request { ); // if the old vault has not been liquidated, give it back its griefing collateral - ICollateral.releaseCollateral(oldVaultId, replace.griefingCollateral); + vaultRegistry.releaseCollateral(oldVaultId, replace.griefingCollateral); // Emit ExecuteReplace event. emit ExecuteReplace(replaceId, oldVaultId, newVaultId); diff --git a/contract/bridge/contracts/Request.sol b/contract/bridge/contracts/Request.sol index aebfe1b..3ff933f 100644 --- a/contract/bridge/contracts/Request.sol +++ b/contract/bridge/contracts/Request.sol @@ -2,7 +2,7 @@ pragma solidity 0.6.12; -abstract contract Request { +contract Request { enum RequestStatus { None, Pending, diff --git a/contract/bridge/contracts/StakedRelayer.sol b/contract/bridge/contracts/StakedRelayer.sol index 31b7cf4..2ddc552 100644 --- a/contract/bridge/contracts/StakedRelayer.sol +++ b/contract/bridge/contracts/StakedRelayer.sol @@ -4,10 +4,7 @@ import {IRelay} from "./IRelay.sol"; import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@interlay/bitcoin-spv-sol/contracts/BTCUtils.sol"; - -interface IVaultRegistry { - function liquidateTheftVault(address vaultId, address reporterId) external; -} +import "./IVaultRegistry.sol"; contract StakedRelayer is Initializable, OwnableUpgradeable { using BTCUtils for bytes; diff --git a/contract/bridge/contracts/TxValidate.sol b/contract/bridge/contracts/TxValidate.sol index e57af2a..c71dded 100644 --- a/contract/bridge/contracts/TxValidate.sol +++ b/contract/bridge/contracts/TxValidate.sol @@ -19,7 +19,7 @@ library TxValidate { ) internal pure returns (uint256) { uint256 btcAmount; address btcAddress; - + if (opReturnId != 0x0) { (, uint256 _nVouts) = txVout.parseVarInt(); uint256 voutCount = _nVouts; diff --git a/contract/bridge/contracts/VaultRegistry.sol b/contract/bridge/contracts/VaultRegistry.sol index b849324..03600fb 100644 --- a/contract/bridge/contracts/VaultRegistry.sol +++ b/contract/bridge/contracts/VaultRegistry.sol @@ -2,16 +2,19 @@ pragma solidity 0.6.12; -import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/math/MathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import {ICollateral} from "./Collateral.sol"; import {BitcoinKeyDerivation} from "./crypto/BitcoinKeyDerivation.sol"; import "./IExchangeRateOracle.sol"; +import "./IVaultRegistry.sol"; -abstract contract VaultRegistry is Initializable, ICollateral { +contract VaultRegistry is ReentrancyGuardUpgradeable, OwnableUpgradeable, ICollateral { using SafeMathUpgradeable for uint256; + // VaultRegistry struct Vault { uint256 btcPublicKeyX; uint256 btcPublicKeyY; @@ -22,11 +25,12 @@ abstract contract VaultRegistry is Initializable, ICollateral { uint256 replaceCollateral; uint256 toBeReplaced; uint256 liquidatedCollateral; - mapping(address => bool) depositAddresses; } mapping(address => Vault) public vaults; + mapping(address => mapping(address => bool)) vaultDepositAddress; IExchangeRateOracle oracle; + address public oneBtcAddress; event RegisterVault( address indexed vaultId, @@ -34,7 +38,6 @@ abstract contract VaultRegistry is Initializable, ICollateral { uint256 btcPublicKeyX, uint256 btcPublicKeyY ); - event VaultPublicKeyUpdate(address indexed vaultId, uint256 x, uint256 y); event IncreaseToBeIssuedTokens(address indexed vaultId, uint256 amount); event IncreaseToBeRedeemedTokens(address indexed vaultId, uint256 amount); @@ -50,9 +53,37 @@ abstract contract VaultRegistry is Initializable, ICollateral { uint256 collateral ); event LiquidateVault(); + event UpdateOneBtcAddress(address indexed by, address indexed oldOneBtc, address indexed newOneBtc); + event UpdateOracleAddress(address indexed by, address indexed oldOracle, address indexed newOracle); + + modifier onlyOneBtc() { + require(msg.sender == oneBtcAddress, "OnlyOneBtc"); + _; + } + + function initialize(IExchangeRateOracle _oracle) external initializer { + __ReentrancyGuard_init(); + __Ownable_init(); + + oracle = _oracle; + } + + function updateOneBtcAddress(address _oneBtcAddress) public onlyOwner { + UpdateOneBtcAddress(msg.sender, oneBtcAddress, _oneBtcAddress); + + require(_oneBtcAddress != address(0), "Zero address for OneBtc"); + oneBtcAddress = _oneBtcAddress; + } + + function updateOracleAddress(IExchangeRateOracle _oracle) external onlyOwner { + UpdateOracleAddress(msg.sender, address(oracle), address(_oracle)); + + require(address(_oracle) != address(0), "Zero address for Oracle"); + oracle = _oracle; + } function registerVault(uint256 btcPublicKeyX, uint256 btcPublicKeyY) - external + public payable { address vaultId = msg.sender; @@ -70,7 +101,8 @@ abstract contract VaultRegistry is Initializable, ICollateral { } function registerDepositAddress(address vaultId, uint256 issueId) - internal + public + onlyOneBtc returns (address) { Vault storage vault = vaults[vaultId]; @@ -82,10 +114,10 @@ abstract contract VaultRegistry is Initializable, ICollateral { ); require( - !vault.depositAddresses[derivedKey], + !vaultDepositAddress[vaultId][derivedKey], "The btc address is already used" ); - vault.depositAddresses[derivedKey] = true; + vaultDepositAddress[vaultId][derivedKey] = true; return derivedKey; } @@ -95,7 +127,7 @@ abstract contract VaultRegistry is Initializable, ICollateral { uint256 btcPublicKeyX, uint256 btcPublicKeyY, uint256 replaceId - ) internal returns (address) { + ) public onlyOneBtc returns (address) { Vault storage vault = vaults[vaultId]; require(vault.btcPublicKeyX != 0, "Vault does not exist"); @@ -106,16 +138,16 @@ abstract contract VaultRegistry is Initializable, ICollateral { ); require( - !vault.depositAddresses[btcAddress], + !vaultDepositAddress[vaultId][btcAddress], "The btc address is already used" ); - vault.depositAddresses[btcAddress] = true; + vaultDepositAddress[vaultId][btcAddress] = true; return btcAddress; } function updatePublicKey(uint256 btcPublicKeyX, uint256 btcPublicKeyY) - external + public { address vaultId = msg.sender; Vault storage vault = vaults[vaultId]; @@ -130,18 +162,19 @@ abstract contract VaultRegistry is Initializable, ICollateral { Vault storage vault = vaults[vaultId]; require(vault.btcPublicKeyX != 0, "Vault does not exist"); vault.collateral = vault.collateral.add(msg.value); - ICollateral.lockCollateral(vaultId, msg.value); + ICollateral._lockCollateral(vaultId, msg.value); } - function withdrawCollateral(uint256 amount) external { + function withdrawCollateral(uint256 amount) public nonReentrant { Vault storage vault = vaults[msg.sender]; require(vault.btcPublicKeyX != 0, "Vault does not exist"); vault.collateral = vault.collateral.sub(amount); - ICollateral.releaseCollateral(msg.sender, amount); + ICollateral._releaseCollateral(msg.sender, amount); } function decreaseToBeIssuedTokens(address vaultId, uint256 amount) - internal + public + onlyOneBtc { Vault storage vault = vaults[vaultId]; vault.toBeIssued = vault.toBeIssued.sub(amount); @@ -149,7 +182,8 @@ abstract contract VaultRegistry is Initializable, ICollateral { } function tryIncreaseToBeIssuedTokens(address vaultId, uint256 amount) - internal + public + onlyOneBtc returns (bool) { uint256 issuableTokens = issuableTokens(vaultId); @@ -161,7 +195,8 @@ abstract contract VaultRegistry is Initializable, ICollateral { } function tryIncreaseToBeRedeemedTokens(address vaultId, uint256 amount) - internal + public + onlyOneBtc returns (bool) { uint256 redeemable = redeemableTokens(vaultId); @@ -177,7 +212,7 @@ abstract contract VaultRegistry is Initializable, ICollateral { return vault.issued.sub(vault.toBeRedeemed); } - function redeemTokens(address vaultId, uint256 amount) internal { + function redeemTokens(address vaultId, uint256 amount) public onlyOneBtc { Vault storage vault = vaults[vaultId]; vault.toBeRedeemed = vault.toBeRedeemed.sub(amount); vault.issued = vault.issued.sub(amount); @@ -185,14 +220,14 @@ abstract contract VaultRegistry is Initializable, ICollateral { } function issuableTokens(address vaultId) public view returns (uint256) { - uint256 freeCollateral = ICollateral.getFreeCollateral(vaultId); + uint256 freeCollateral = ICollateral._getFreeCollateral(vaultId); return oracle.collateralToWrapped( freeCollateral.mul(100).div(secureCollateralThreshold()) ); } - function issueTokens(address vaultId, uint256 amount) internal { + function issueTokens(address vaultId, uint256 amount) public onlyOneBtc { Vault storage vault = vaults[vaultId]; vault.issued = vault.issued.add(amount); vault.toBeIssued = vault.toBeIssued.sub(amount); @@ -203,7 +238,7 @@ abstract contract VaultRegistry is Initializable, ICollateral { uint256 collateral, uint256 numerator, uint256 denominator - ) internal view returns (uint256 amount) { + ) public view returns (uint256 amount) { if (numerator == 0 && denominator == 0) { return collateral; } @@ -212,7 +247,17 @@ abstract contract VaultRegistry is Initializable, ICollateral { } function requestableToBeReplacedTokens(address vaultId) + public + view + onlyOneBtc + returns (uint256 amount) + { + return requestableToBeReplacedTokensFromSelf(vaultId); + } + + function requestableToBeReplacedTokensFromSelf(address vaultId) internal + view returns (uint256 amount) { Vault memory vault = vaults[vaultId]; @@ -229,10 +274,10 @@ abstract contract VaultRegistry is Initializable, ICollateral { address vaultId, uint256 tokens, uint256 collateral - ) internal returns (uint256, uint256) { + ) public onlyOneBtc returns (uint256, uint256) { Vault storage vault = vaults[vaultId]; - uint256 requestableIncrease = requestableToBeReplacedTokens(vaultId); + uint256 requestableIncrease = requestableToBeReplacedTokensFromSelf(vaultId); require( tokens <= requestableIncrease, @@ -248,7 +293,8 @@ abstract contract VaultRegistry is Initializable, ICollateral { } function decreaseToBeReplacedTokens(address vaultId, uint256 tokens) - internal + public + onlyOneBtc returns (uint256, uint256) { Vault storage vault = vaults[vaultId]; @@ -279,7 +325,7 @@ abstract contract VaultRegistry is Initializable, ICollateral { address newVaultId, uint256 tokens, uint256 collateral - ) internal { + ) public onlyOneBtc { Vault storage oldVault = vaults[oldVaultId]; Vault storage newVault = vaults[newVaultId]; @@ -295,11 +341,11 @@ abstract contract VaultRegistry is Initializable, ICollateral { emit ReplaceTokens(oldVaultId, newVaultId, tokens, collateral); } - function tryDepositCollateral(address vaultId, uint256 amount) internal { + function tryDepositCollateral(address vaultId, uint256 amount) public onlyOneBtc { Vault storage vault = vaults[vaultId]; require(vault.btcPublicKeyX != 0, "Vault does not exist"); - ICollateral.lockCollateral(vaultId, amount); + ICollateral._lockCollateral(vaultId, amount); // Self::increase_total_backing_collateral(amount)?; @@ -321,8 +367,8 @@ abstract contract VaultRegistry is Initializable, ICollateral { liquidateVault.collateral = liquidateVault.collateral.add(amount); vault.collateral = vault.collateral.sub(amount); // slash collateral - ICollateral.slashCollateral(vaultId, address(this), amount); - ICollateral.lockCollateral(address(this), amount); // TODO; double check + ICollateral._slashCollateral(vaultId, address(this), amount); + ICollateral._lockCollateral(address(this), amount); // TODO; double check } function backedTokens(address vaultId) private returns (uint256) { @@ -398,9 +444,75 @@ abstract contract VaultRegistry is Initializable, ICollateral { /** * @dev Liquidate a vault by transferring all of its token balances to the liquidation vault. */ - function liquidateVault(address vaultId, address reporterId) internal { + function liquidateVault(address vaultId, address reporterId) public { liquidate(vaultId, reporterId); } + /// override functions for ICollateral + function lockCollateral(address sender, uint256 amount) public virtual onlyOneBtc { + ICollateral._lockCollateral(sender, amount); + } + + function releaseCollateral(address sender, uint256 amount) public virtual onlyOneBtc nonReentrant { + ICollateral._releaseCollateral(sender, amount); + } + + function slashCollateral( + address from, + address to, + uint256 amount + ) public virtual onlyOneBtc nonReentrant { + ICollateral._slashCollateral(from, to, amount); + } + + function useCollateralInc(address vaultId, uint256 amount) public virtual onlyOneBtc { + ICollateral._useCollateralInc(vaultId, amount); + } + + function useCollateralDec(address vaultId, uint256 amount) public virtual onlyOneBtc { + ICollateral._useCollateralDec(vaultId, amount); + } + + // set functions for Vault + // function setBtcPublicKeyX(address vaultAddress, uint256 value) external onlyOneBtc { + // vaults[vaultAddress].btcPublicKeyX = value; + // } + + // function setBtcPublicKeyY(address vaultAddress, uint256 value) external onlyOneBtc { + // vaults[vaultAddress].btcPublicKeyY = value; + // } + + // function setCollateral(address vaultAddress, uint256 value) external onlyOneBtc { + // vaults[vaultAddress].collateral = value; + // } + + // function setIssued(address vaultAddress, uint256 value) external onlyOneBtc { + // vaults[vaultAddress].issued = value; + // } + + // function setToBeIssued(address vaultAddress, uint256 value) external onlyOneBtc { + // vaults[vaultAddress].toBeIssued = value; + // } + + // function setToBeRedeemed(address vaultAddress, uint256 value) external onlyOneBtc { + // vaults[vaultAddress].toBeRedeemed = value; + // } + + // function setReplaceCollateral(address vaultAddress, uint256 value) external onlyOneBtc { + // vaults[vaultAddress].replaceCollateral = value; + // } + + // function setToBeReplaced(address vaultAddress, uint256 value) external onlyOneBtc { + // vaults[vaultAddress].toBeReplaced = value; + // } + + // function setLiquidatedCollateral(address vaultAddress, uint256 value) external onlyOneBtc { + // vaults[vaultAddress].liquidatedCollateral = value; + // } + + // function setVaultDepositAddress(address vaultAddress, address derivedKey, bool value) external onlyOneBtc { + // vaults[vaultAddress][derivedKey] = value; + // } + uint256[45] private __gap; } diff --git a/contract/bridge/contracts/mock/TxValidateMock.sol b/contract/bridge/contracts/mock/TxValidateMock.sol index c3a5b02..c1e0772 100644 --- a/contract/bridge/contracts/mock/TxValidateMock.sol +++ b/contract/bridge/contracts/mock/TxValidateMock.sol @@ -9,7 +9,8 @@ contract TxValidateMock { bytes memory txVout, uint256 minimumBtc, address recipientBtcAddress, - uint256 opReturnId + uint256 opReturnId, + uint256 outputIndex ) public pure returns (uint256) { return TxValidate.validateTransaction( @@ -17,7 +18,7 @@ contract TxValidateMock { minimumBtc, recipientBtcAddress, opReturnId, - 0 + outputIndex ); } } diff --git a/contract/bridge/contracts/test/CollateralTestWrapper.sol b/contract/bridge/contracts/test/CollateralTestWrapper.sol index 0b256b7..aa01e83 100644 --- a/contract/bridge/contracts/test/CollateralTestWrapper.sol +++ b/contract/bridge/contracts/test/CollateralTestWrapper.sol @@ -5,11 +5,11 @@ import {ICollateral} from "../Collateral.sol"; contract CollateralTestWrapper is ICollateral { function testLockCollateral(address sender, uint256 amount) public payable { - return lockCollateral(sender, amount); + return _lockCollateral(sender, amount); } function testReleaseCollateral(address sender, uint256 amount) public { - return releaseCollateral(sender, amount); + return _releaseCollateral(sender, amount); } function testSlashCollateral( @@ -17,7 +17,7 @@ contract CollateralTestWrapper is ICollateral { address to, uint256 amount ) public { - return slashCollateral(from, to, amount); + return _slashCollateral(from, to, amount); } function testGetFreeCollateral(address vaultId) @@ -25,14 +25,14 @@ contract CollateralTestWrapper is ICollateral { view returns (uint256) { - return getFreeCollateral(vaultId); + return _getFreeCollateral(vaultId); } function testUseCollateralInc(address vaultId, uint256 amount) public { - return useCollateralInc(vaultId, amount); + return _useCollateralInc(vaultId, amount); } function testUseCollateralDec(address vaultId, uint256 amount) public { - return useCollateralDec(vaultId, amount); + return _useCollateralDec(vaultId, amount); } } diff --git a/contract/bridge/contracts/test/ExchangeRateOracleWrapper.sol b/contract/bridge/contracts/test/ExchangeRateOracleWrapper.sol index 6e70169..2edb679 100644 --- a/contract/bridge/contracts/test/ExchangeRateOracleWrapper.sol +++ b/contract/bridge/contracts/test/ExchangeRateOracleWrapper.sol @@ -25,9 +25,9 @@ contract ExchangeRateOracleWrapper is Initializable { } /** - @notice Set the latest (aggregate) BTC/ONE exchange rate. This function invokes a check of vault collateral rates in the Vault Registry component. - @param rate uint256 BTC/ONE exchange rate. - */ + * @notice Set the latest (aggregate) BTC/ONE exchange rate. This function invokes a check of vault collateral rates in the Vault Registry component. + * @param rate uint256 BTC/ONE exchange rate. + */ function setExchangeRate(uint256 rate) public { exchangeRate = rate; lastExchangeRateTime = now; @@ -36,9 +36,9 @@ contract ExchangeRateOracleWrapper is Initializable { } /** - @notice Returns the latest BTC/ONE exchange rate, as received from the external data sources. - @return uint256 (aggregate) exchange rate value - */ + * @notice Returns the latest BTC/ONE exchange rate, as received from the external data sources. + * @return uint256 (aggregate) exchange rate value + */ function getExchangeRate() public view returns (uint256) { require( now - lastExchangeRateTime > MAX_DELAY, diff --git a/contract/bridge/contracts/test/VaultRegistryTestWrapper.sol b/contract/bridge/contracts/test/VaultRegistryWrapper.sol similarity index 91% rename from contract/bridge/contracts/test/VaultRegistryTestWrapper.sol rename to contract/bridge/contracts/test/VaultRegistryWrapper.sol index 1cf467f..235104a 100644 --- a/contract/bridge/contracts/test/VaultRegistryTestWrapper.sol +++ b/contract/bridge/contracts/test/VaultRegistryWrapper.sol @@ -3,7 +3,7 @@ pragma solidity 0.6.12; import {VaultRegistry} from "../VaultRegistry.sol"; -contract VaultRegistryTestWrapper is VaultRegistry { +contract VaultRegistryWrapper is VaultRegistry { event RedeemableTokens(address vaultId, uint256 amount); event RequestableToBeReplacedTokens(address vaultId, uint256 amount); @@ -29,7 +29,7 @@ contract VaultRegistryTestWrapper is VaultRegistry { public returns (bool) { - uint256 issuableTokens = getFreeCollateral(vaultId).mul(100).div(150); // mock oracle + uint256 issuableTokens = _getFreeCollateral(vaultId).mul(100).div(150); // mock oracle if (issuableTokens < amount) return false; // ExceedingVaultLimit Vault storage vault = vaults[vaultId]; vault.toBeIssued = vault.toBeIssued.add(amount); @@ -62,7 +62,7 @@ contract VaultRegistryTestWrapper is VaultRegistry { view returns (uint256) { - return getFreeCollateral(vaultId); + return _getFreeCollateral(vaultId); } function testRequestableToBeReplacedTokens(address vaultId) public returns (uint256) { diff --git a/contract/bridge/migrations/3_deploy_onebtc.js b/contract/bridge/migrations/3_deploy_onebtc.js index c1e6d5b..37ca454 100644 --- a/contract/bridge/migrations/3_deploy_onebtc.js +++ b/contract/bridge/migrations/3_deploy_onebtc.js @@ -1,12 +1,23 @@ const { deployProxy } = require('@openzeppelin/truffle-upgrades'); -const OneBtc = artifacts.require("OneBtc"); const RelayMock = artifacts.require("RelayMock"); const ExchangeRateOracleWrapper = artifacts.require("ExchangeRateOracleWrapper"); +const VaultRegistry = artifacts.require("VaultRegistry"); +const OneBtc = artifacts.require("OneBtc"); module.exports = async function(deployer) { - const IRelay = await RelayMock.deployed(); - const IExchangeRateOracleWrapper = await ExchangeRateOracleWrapper.deployed(); + // RelayMock + const relayMock = await RelayMock.deployed(); + console.log("RelayMock contract deployed to: ", relayMock.address); + + // ExchangeRateOracleWrapper + const exchangeRateOracleWrapper = await deployProxy(ExchangeRateOracleWrapper); + console.log("ExchangeRateOracleWrapper contract deployed to: ", exchangeRateOracleWrapper.address); + + // VaultRegistry + const vaultRegistry = await deployProxy(VaultRegistry, [exchangeRateOracleWrapper.address]); + console.log("VaultRegistry contract deployed to: ", vaultRegistry.address); - const c = await deployProxy(OneBtc, [IRelay.address, IExchangeRateOracleWrapper.address], { deployer } ); - console.log(c.address) + // OneBtc + const oneBtc = await deployProxy(OneBtc, [relayMock.address, exchangeRateOracleWrapper.address, vaultRegistry.address]); + console.log("OneBtc contract deployed to: ", oneBtc.address); }; diff --git a/contract/bridge/scripts/deploy_onebtc_proxy.js b/contract/bridge/scripts/deploy_onebtc_proxy.js index ad962b5..9beb406 100644 --- a/contract/bridge/scripts/deploy_onebtc_proxy.js +++ b/contract/bridge/scripts/deploy_onebtc_proxy.js @@ -11,10 +11,15 @@ async function main() { process.env.EXCHANGE_RATE_ORACLE ); - const OneBtc = await ethers.getContractFactory("OneBtc"); - const oneBtc = await upgrades.deployProxy(OneBtc, [relay.address, oracle.address], { initializer: "initialize" }); + const VaultRegistry = await ethers.getContractFactory("VaultRegistry"); + const vaultRegistry = await upgrades.deployProxy(VaultRegistry, [exchangeRateOracle.address]); + await vaultRegistry.deployed(); + console.log("VaultRegistry contract deployed to: ", vaultRegistry.address); - console.log("OneBtc deployed to:", oneBtc.address); + const OneBtc = await ethers.getContractFactory("OneBtc"); + const oneBtc = await upgrades.deployProxy(OneBtc, [relayMock.address, exchangeRateOracle.address, vaultRegistry.address]); + await oneBtc.deployed(); + console.log("OneBtc contract deployed to: ", oneBtc.address); } main() diff --git a/contract/bridge/scripts/deploy_oracle.js b/contract/bridge/scripts/deploy_oracle.js index aece703..61114f8 100644 --- a/contract/bridge/scripts/deploy_oracle.js +++ b/contract/bridge/scripts/deploy_oracle.js @@ -1,3 +1,5 @@ + + const { ethers } = require("hardhat"); const Web3 = require("web3"); const web3 = new Web3(); diff --git a/contract/bridge/test/Collateral.js b/contract/bridge/test/Collateral.js index 6c5603b..1d42fc9 100644 --- a/contract/bridge/test/Collateral.js +++ b/contract/bridge/test/Collateral.js @@ -1,4 +1,5 @@ const BN = require("bn.js"); +const { expectRevert } = require("@openzeppelin/test-helpers"); const CollateralTestWrapper = artifacts.require("CollateralTestWrapper"); web3.extend({ @@ -33,22 +34,15 @@ contract("Collateral unit test", (accounts) => { }); it("Error on lockCollateral with 0 amount", async function () { - let errorMessage = ""; - - try { - await this.CollateralTestWrapper.testLockCollateral( - this.vaultId, - this.lockAmount, + const lockAmount = 0; + await expectRevert(this.CollateralTestWrapper.testLockCollateral( + this.vaultId, + lockAmount, { from: accounts[0], value: 0, } - ); - } catch (e) { - errorMessage = e.message.split("Reason given: ")[1]; - } - - assert.equal(errorMessage, "Invalid collateral."); + ), 'Invalid collateral'); }); it("LockCollateral with 1 Gwei amount", async function () { diff --git a/contract/bridge/test/Issue.test.js b/contract/bridge/test/Issue.test.js index 944fbcf..609ae37 100644 --- a/contract/bridge/test/Issue.test.js +++ b/contract/bridge/test/Issue.test.js @@ -1,7 +1,10 @@ const BN = require("bn.js"); const { expectRevert } = require("@openzeppelin/test-helpers"); const { web3 } = require("@openzeppelin/test-helpers/src/setup"); +const { deployProxy } = require("@openzeppelin/truffle-upgrades"); +const ExchangeRateOracleWrapper = artifacts.require("ExchangeRateOracleWrapper"); +const VaultRegistry = artifacts.require("VaultRegistry"); const OneBtc = artifacts.require("OneBtc"); const RelayMock = artifacts.require("RelayMock"); const { issueTxMock } = require("./mock/btcTxMock"); @@ -27,8 +30,21 @@ web3.extend({ contract("Issue unit test", (accounts) => { before(async function () { - const IRelay = await RelayMock.new(); - this.OneBtc = await OneBtc.new(IRelay.address); + // get contracts + this.RelayMock = await RelayMock.new(); + this.ExchangeRateOracleWrapper = await deployProxy(ExchangeRateOracleWrapper); + this.VaultRegistry = await deployProxy(VaultRegistry, [this.ExchangeRateOracleWrapper.address]); + this.OneBtc = await deployProxy(OneBtc, [this.RelayMock.address, this.ExchangeRateOracleWrapper.address, this.VaultRegistry.address]); + + // set OneBtc address to VaultRegistry + this.VaultRegistry.updateOneBtcAddress(this.OneBtc.address); + + // set BTC/ONE exchange rate + await this.ExchangeRateOracleWrapper.setExchangeRate(10); // 1 OneBtc = 10 ONE + + // increase time to be enable exchange rate + await web3.miner.incTime(Number(1001)); // MAX_DELAY = 1000 + await web3.miner.mine(); this.vaultId = accounts[1]; this.issueRequester = accounts[2]; @@ -37,48 +53,49 @@ contract("Issue unit test", (accounts) => { this.OneBtcBalanceVault = 0; }); - it("Error on requestIssue with the exceeding vault limit", async function () { - const IssueAmount = Math.floor(10 * 1e8 * 100 / 150) + 1; // threshold = 150 - - await expectRevert(this.OneBtc.requestIssue(IssueAmount, this.vaultId, { - from: this.issueRequester, - value: IssueAmount, - }), 'ExceedingVaultLimit'); - }); - - it("Register Vault with 10 Wei Collateral", async function () { + it("Register Vault with 100e18 Wei Collateral", async function () { const VaultEcPair = bitcoin.ECPair.makeRandom({ compressed: false }); const pubX = bn(VaultEcPair.publicKey.slice(1, 33)); const pubY = bn(VaultEcPair.publicKey.slice(33, 65)); - const collateral = 10 * 1e8; - await this.OneBtc.registerVault(pubX, pubY, { + const collateral = new BN('10000000000000000000'); // 10e18, 10 ONE + await this.VaultRegistry.registerVault(pubX, pubY, { from: this.vaultId, value: collateral, }); - const vault = await this.OneBtc.vaults(this.vaultId); + const vault = await this.VaultRegistry.vaults(this.vaultId); assert.equal(pubX.toString(), vault.btcPublicKeyX.toString()); assert.equal(pubX.toString(), vault.btcPublicKeyX.toString()); - assert.equal(collateral, vault.collateral.toString()); + assert.equal(collateral.toString(), vault.collateral.toString()); }); it("Error on requestIssue with the insufficient collateral", async function () { - const IssueAmount = 1 * 1e8; + const IssueAmount = new BN('100000000'); // 1e8, 1 OneBtc await expectRevert(this.OneBtc.requestIssue(IssueAmount+1, this.vaultId, { from: this.issueRequester, value: IssueAmount, - }), 'InsufficientCollateral'); + }), 'Insufficient collateral'); }); - it("Issue 1 BTC", async function () { - const IssueAmount = 1 * 1e8; + it("Error on requestIssue with the exceeding vault limit", async function () { + const collateral = new BN('10000000000000000000'); // 10e18, 10 ONE + const IssueAmount = Math.floor(collateral * 100 / 150) + 1; // threshold = 150 + + await expectRevert(this.OneBtc.requestIssue(IssueAmount.toString(), this.vaultId, { + from: this.issueRequester, + value: IssueAmount.toString(), + }), 'Amount requested exceeds vault limit'); + }); + + it("Issue 0.1 BTC", async function () { + const IssueAmount = new BN('10000000'); // 0.1e8, 0.1 OneBtc const IssueReq = await this.OneBtc.requestIssue(IssueAmount, this.vaultId, { from: this.issueRequester, value: IssueAmount, }); const IssueEvent = IssueReq.logs.filter( - (log) => log.event == "IssueRequest" + (log) => log.event == "IssueRequested" )[0]; const issueId = IssueEvent.args.issueId; const btcAddress = IssueEvent.args.btcAddress; @@ -86,19 +103,21 @@ contract("Issue unit test", (accounts) => { Buffer.from(btcAddress.slice(2), "hex"), 0 ); - const btcTx = issueTxMock(issueId, btcBase58, IssueAmount); + const btcTx = issueTxMock(issueId, btcBase58, IssueAmount.toNumber()); const btcBlockNumberMock = 1000; const btcTxIndexMock = 2; const heightAndIndex = (btcBlockNumberMock << 32) | btcTxIndexMock; const headerMock = Buffer.alloc(0); const proofMock = Buffer.alloc(0); + const ouputIndexMock = 0; const ExecuteReq = await this.OneBtc.executeIssue( this.issueRequester, issueId, proofMock, btcTx.toBuffer(), heightAndIndex, - headerMock + headerMock, + ouputIndexMock ); this.OneBtcBalance = await this.OneBtc.balanceOf(this.issueRequester); @@ -107,7 +126,7 @@ contract("Issue unit test", (accounts) => { assert.equal(this.OneBtcBalanceVault.toString(), IssueEvent.args.fee.toString()); const ExecuteEvent = ExecuteReq.logs.filter( - (log) => log.event == "IssueComplete" + (log) => log.event == "IssueCompleted" )[0]; assert.equal(ExecuteEvent.args.issuedId.toString(), issueId.toString()); @@ -118,24 +137,25 @@ contract("Issue unit test", (accounts) => { proofMock, btcTx.toBuffer(), heightAndIndex, - headerMock - ), 'request is completed'); + headerMock, + ouputIndexMock + ), 'Request is already completed'); // should not cancel the request which has been already completed await expectRevert(this.OneBtc.cancelIssue( this.issueRequester, issueId - ), 'request is completed'); + ), 'Request is already completed'); }); it("Error on requester is not a executor of issue call", async function () { - const IssueAmount = 1 * 1e8; + const IssueAmount = new BN('10000000'); // 0.1e8, 0.1 OneBtc const IssueReq = await this.OneBtc.requestIssue(IssueAmount, this.vaultId, { from: this.issueRequester, value: IssueAmount, }); const IssueEvent = IssueReq.logs.filter( - (log) => log.event == "IssueRequest" + (log) => log.event == "IssueRequested" )[0]; const issueId = IssueEvent.args.issueId; const btcAddress = IssueEvent.args.btcAddress; @@ -149,25 +169,26 @@ contract("Issue unit test", (accounts) => { const heightAndIndex = (btcBlockNumberMock << 32) | btcTxIndexMock; const headerMock = Buffer.alloc(0); const proofMock = Buffer.alloc(0); - + const ouputIndexMock = 0; await expectRevert(this.OneBtc.executeIssue( this.issueRequester, issueId, proofMock, btcTx.toBuffer(), heightAndIndex, - headerMock - ), 'InvalidExecutor'); + headerMock, + ouputIndexMock + ), 'Invalid executor'); }); it("Slash in case the transferred BTC amount is smaller than the requested OneBTC amount", async function () { - const IssueAmount = 1 * 1e8; + const IssueAmount = new BN('10000000'); // 0.1e8, 0.1 OneBtc const IssueReq = await this.OneBtc.requestIssue(IssueAmount, this.vaultId, { from: this.issueRequester, value: IssueAmount, }); const IssueEvent = IssueReq.logs.filter( - (log) => log.event == "IssueRequest" + (log) => log.event == "IssueRequested" )[0]; const issueId = IssueEvent.args.issueId; const btcAddress = IssueEvent.args.btcAddress; @@ -181,6 +202,7 @@ contract("Issue unit test", (accounts) => { const heightAndIndex = (btcBlockNumberMock << 32) | btcTxIndexMock; const headerMock = Buffer.alloc(0); const proofMock = Buffer.alloc(0); + const ouputIndexMock = 0; const ExecuteReq = await this.OneBtc.executeIssue( this.issueRequester, issueId, @@ -188,14 +210,11 @@ contract("Issue unit test", (accounts) => { btcTx.toBuffer(), heightAndIndex, headerMock, + ouputIndexMock, { from: this.issueRequester } ); - const ExecuteEvent = ExecuteReq.logs.filter( - (log) => log.event == "SlashCollateral" - )[0]; - assert.equal(ExecuteEvent.args.amount, IssueAmount - (IssueAmount / 4)); const beforeOneBtcBalance = this.OneBtcBalance; const beforeOneBtcBalanceVault = this.OneBtcBalanceVault; @@ -206,13 +225,13 @@ contract("Issue unit test", (accounts) => { }); it("Error on cancelIssue with the invalid cancel period", async function () { - const IssueAmount = 1 * 1e8; + const IssueAmount = new BN('10000000'); // 0.1e8, 0.1 OneBtc const IssueReq = await this.OneBtc.requestIssue(IssueAmount, this.vaultId, { from: this.issueRequester, value: IssueAmount, }); const IssueEvent = IssueReq.logs.filter( - (log) => log.event == "IssueRequest" + (log) => log.event == "IssueRequested" )[0]; const issueId = IssueEvent.args.issueId; const btcAddress = IssueEvent.args.btcAddress; @@ -221,17 +240,17 @@ contract("Issue unit test", (accounts) => { 0 ); - await expectRevert(this.OneBtc.cancelIssue(this.issueRequester, issueId), 'TimeNotExpired'); + await expectRevert(this.OneBtc.cancelIssue(this.issueRequester, issueId), 'Time not expired'); }); it("Cancel issue", async function () { - const IssueAmount = 1 * 1e8; + const IssueAmount = new BN('10000000'); // 0.1e8, 0.1 OneBtc const IssueReq = await this.OneBtc.requestIssue(IssueAmount, this.vaultId, { from: this.issueRequester, value: IssueAmount, }); const IssueEvent = IssueReq.logs.filter( - (log) => log.event == "IssueRequest" + (log) => log.event == "IssueRequested" )[0]; const issueId = IssueEvent.args.issueId; const btcAddress = IssueEvent.args.btcAddress; @@ -246,7 +265,7 @@ contract("Issue unit test", (accounts) => { const CancelReq = await this.OneBtc.cancelIssue(this.issueRequester, issueId); const CancelEvent = CancelReq.logs.filter( - (log) => log.event == "IssueCancel" + (log) => log.event == "IssueCanceled" )[0]; assert.equal(CancelEvent.args.issuedId.toString(), issueId.toString()); }); diff --git a/contract/bridge/test/OneBTC.test.js b/contract/bridge/test/OneBTC.test.js index f31752d..7c4553a 100644 --- a/contract/bridge/test/OneBTC.test.js +++ b/contract/bridge/test/OneBTC.test.js @@ -1,9 +1,15 @@ +const BN = require("bn.js"); +const { expectRevert } = require("@openzeppelin/test-helpers"); +const { web3 } = require("@openzeppelin/test-helpers/src/setup"); +const { deployProxy } = require("@openzeppelin/truffle-upgrades"); + +const ExchangeRateOracleWrapper = artifacts.require("ExchangeRateOracleWrapper"); +const VaultRegistry = artifacts.require("VaultRegistry"); const OneBtc = artifacts.require("OneBtc"); const RelayMock = artifacts.require("RelayMock"); +const { issueTxMock } = require("./mock/btcTxMock"); -const { expectRevert } = require("@openzeppelin/test-helpers"); const bitcoin = require('bitcoinjs-lib'); -const { issueTxMock } = require('./mock/btcTxMock'); const bn=b=>BigInt(`0x${b.toString('hex')}`); web3.extend({ @@ -21,9 +27,22 @@ web3.extend({ contract("issue/redeem test", accounts => { before(async function() { - this.name="name"; - const IRelay = await RelayMock.new(); - this.OneBtc = await OneBtc.new(IRelay.address); + // get contracts + this.RelayMock = await RelayMock.new(); + this.ExchangeRateOracleWrapper = await deployProxy(ExchangeRateOracleWrapper); + this.VaultRegistry = await deployProxy(VaultRegistry, [this.ExchangeRateOracleWrapper.address]); + this.OneBtc = await deployProxy(OneBtc, [this.RelayMock.address, this.ExchangeRateOracleWrapper.address, this.VaultRegistry.address]); + + // set OneBtc address to VaultRegistry + this.VaultRegistry.updateOneBtcAddress(this.OneBtc.address); + + // set BTC/ONE exchange rate + await this.ExchangeRateOracleWrapper.setExchangeRate(10); // 1 OneBtc = 10 ONE + + // increase time to be enable exchange rate + await web3.miner.incTime(Number(1001)); // MAX_DELAY = 1000 + await web3.miner.mine(); + this.VaultEcPair = bitcoin.ECPair.makeRandom({compressed:false}); this.vaultId = accounts[1]; this.issueRequester = accounts[2]; @@ -36,26 +55,27 @@ contract("issue/redeem test", accounts => { const pubX = bn(this.VaultEcPair.publicKey.slice(1, 33)); const pubY = bn(this.VaultEcPair.publicKey.slice(33, 65)); const collateral = web3.utils.toWei('10'); - await this.OneBtc.registerVault(pubX, pubY, {from:this.vaultId, value: collateral}); - const vault = await this.OneBtc.vaults(this.vaultId); + await this.VaultRegistry.registerVault(pubX, pubY, {from:this.vaultId, value: collateral}); + const vault = await this.VaultRegistry.vaults(this.vaultId); assert.equal(pubX.toString(), vault.btcPublicKeyX.toString()); assert.equal(pubX.toString(), vault.btcPublicKeyX.toString()); assert.equal(collateral, vault.collateral.toString()); }); it("issue test", async function() { - const IssueAmount = 0.01*1e8; + const IssueAmount = 0.05*1e8; const IssueReq = await this.OneBtc.requestIssue(IssueAmount, this.vaultId, {from: this.issueRequester, value:IssueAmount}); - const IssueEvent = IssueReq.logs.filter(log=>log.event == 'IssueRequest')[0]; + const IssueEvent = IssueReq.logs.filter(log=>log.event == 'IssueRequested')[0]; const issueId = IssueEvent.args.issueId; const btcAddress = IssueEvent.args.btcAddress; const btcBase58 = bitcoin.address.toBase58Check(Buffer.from(btcAddress.slice(2), 'hex'), 0); const btcTx = issueTxMock(issueId, btcBase58, IssueAmount); const btcBlockNumberMock = 1000; const btcTxIndexMock = 2; - const heightAndIndex = btcBlockNumberMock<<32|btcTxIndexMock; + const heightAndIndex = (btcBlockNumberMock << 32) | btcTxIndexMock; const headerMock = Buffer.alloc(0); const proofMock = Buffer.alloc(0); - await this.OneBtc.executeIssue(this.issueRequester, issueId, proofMock, btcTx.toBuffer(), heightAndIndex, headerMock); + const ouputIndexMock = 0; + await this.OneBtc.executeIssue(this.issueRequester, issueId, proofMock, btcTx.toBuffer(), heightAndIndex, headerMock, ouputIndexMock); const OneBtcBalance = await this.OneBtc.balanceOf(this.issueRequester); const OneBtcBalanceVault = await this.OneBtc.balanceOf(this.vaultId); assert.equal(OneBtcBalance.toString(), IssueEvent.args.amount.toString()); @@ -65,7 +85,7 @@ contract("issue/redeem test", accounts => { const RedeemAmount = 0.001*1e8; await this.OneBtc.transfer(this.redeemRequester, RedeemAmount, {from: this.issueRequester}); const RedeemReq = await this.OneBtc.requestRedeem(RedeemAmount, this.redeemBtcAddress, this.vaultId, {from: this.redeemRequester}); - const RedeemEvent = RedeemReq.logs.filter(log=>log.event == 'RedeemRequest')[0]; + const RedeemEvent = RedeemReq.logs.filter(log=>log.event == 'RedeemRequested')[0]; const redeemId = RedeemEvent.args.redeemId; const btcAmount = RedeemEvent.args.amount; @@ -74,20 +94,20 @@ contract("issue/redeem test", accounts => { const btcTx = issueTxMock(redeemId, btcBase58, Number(btcAmount)); const btcBlockNumberMock = 1000; const btcTxIndexMock = 2; - const heightAndIndex = btcBlockNumberMock<<32|btcTxIndexMock; + const btcTxHeight = btcBlockNumberMock<<32; const headerMock = Buffer.alloc(0); const proofMock = Buffer.alloc(0); - const execRedeem = await this.OneBtc.executeRedeem(this.redeemRequester, redeemId, proofMock, btcTx.toBuffer(), heightAndIndex, headerMock); - const redeemEvent = execRedeem.logs.filter(log=>log.event == 'RedeemComplete')[0]; + const execRedeem = await this.OneBtc.executeRedeem(this.redeemRequester, redeemId, proofMock, btcTx.toBuffer(), btcTxHeight, btcTxIndexMock, headerMock); + const redeemEvent = execRedeem.logs.filter(log=>log.event == 'RedeemCompleted')[0]; assert.equal(redeemEvent.args.requester, this.redeemRequester); }); it("cancle issue request test", async function() { const IssueAmount = 0.01*1e8; const IssueReq = await this.OneBtc.requestIssue(IssueAmount, this.vaultId, {from: this.issueRequester, value:IssueAmount}); - const IssueEvent = IssueReq.logs.filter(log=>log.event == 'IssueRequest')[0]; + const IssueEvent = IssueReq.logs.filter(log=>log.event == 'IssueRequested')[0]; const issueId = IssueEvent.args.issueId; const request = await this.OneBtc.issueRequests(this.issueRequester, issueId); - await expectRevert(this.OneBtc.cancelIssue(this.issueRequester, issueId), 'TimeNotExpired'); + await expectRevert(this.OneBtc.cancelIssue(this.issueRequester, issueId), 'Time not expired'); await web3.miner.incTime(Number(request.period)+1); await this.OneBtc.cancelIssue(this.issueRequester, issueId); }); @@ -95,12 +115,12 @@ contract("issue/redeem test", accounts => { const RedeemAmount = 0.001*1e8; await this.OneBtc.transfer(this.redeemRequester, RedeemAmount, {from: this.issueRequester}); const RedeemReq = await this.OneBtc.requestRedeem(RedeemAmount, this.redeemBtcAddress, this.vaultId, {from: this.redeemRequester}); - const RedeemEvent = RedeemReq.logs.filter(log=>log.event == 'RedeemRequest')[0]; + const RedeemEvent = RedeemReq.logs.filter(log=>log.event == 'RedeemRequested')[0]; const redeemId = RedeemEvent.args.redeemId; const request = await this.OneBtc.redeemRequests(this.redeemRequester, redeemId); - await expectRevert(this.OneBtc.cancelRedeem(this.redeemRequester, redeemId), 'TimeNotExpired'); + await expectRevert(this.OneBtc.cancelRedeem(this.redeemRequester, redeemId), 'Time not expired'); await web3.miner.incTime(Number(request.period)+1); await this.OneBtc.cancelRedeem(this.redeemRequester, redeemId); }); diff --git a/contract/bridge/test/Redeem.test.js b/contract/bridge/test/Redeem.test.js index 7834d27..9e1a352 100644 --- a/contract/bridge/test/Redeem.test.js +++ b/contract/bridge/test/Redeem.test.js @@ -1,7 +1,10 @@ const BN = require("bn.js"); const { expectRevert } = require("@openzeppelin/test-helpers"); const { web3 } = require("@openzeppelin/test-helpers/src/setup"); +const { deployProxy } = require("@openzeppelin/truffle-upgrades"); +const ExchangeRateOracleWrapper = artifacts.require("ExchangeRateOracleWrapper"); +const VaultRegistry = artifacts.require("VaultRegistry"); const OneBtc = artifacts.require("OneBtc"); const RelayMock = artifacts.require("RelayMock"); const { issueTxMock } = require("./mock/btcTxMock"); @@ -27,8 +30,21 @@ web3.extend({ contract("Redeem unit test", (accounts) => { before(async function () { - const IRelay = await RelayMock.new(); - this.OneBtc = await OneBtc.new(IRelay.address); + // get contracts + this.RelayMock = await RelayMock.new(); + this.ExchangeRateOracleWrapper = await deployProxy(ExchangeRateOracleWrapper); + this.VaultRegistry = await deployProxy(VaultRegistry, [this.ExchangeRateOracleWrapper.address]); + this.OneBtc = await deployProxy(OneBtc, [this.RelayMock.address, this.ExchangeRateOracleWrapper.address, this.VaultRegistry.address]); + + // set OneBtc address to VaultRegistry + this.VaultRegistry.updateOneBtcAddress(this.OneBtc.address); + + // set BTC/ONE exchange rate + await this.ExchangeRateOracleWrapper.setExchangeRate(10); // 1 OneBtc = 10 ONE + + // increase time to be enable exchange rate + await web3.miner.incTime(Number(1001)); // MAX_DELAY = 1000 + await web3.miner.mine(); this.vaultId = accounts[1]; this.issueRequester = accounts[2]; @@ -42,30 +58,30 @@ contract("Redeem unit test", (accounts) => { this.redeemBtcAddress = '0x'+script.hash.toString('hex'); }); - it("Register Vault with 10 Wei Collateral", async function () { + it("Register Vault with 10e18 Wei Collateral", async function () { const VaultEcPair = bitcoin.ECPair.makeRandom({ compressed: false }); const pubX = bn(VaultEcPair.publicKey.slice(1, 33)); const pubY = bn(VaultEcPair.publicKey.slice(33, 65)); - const collateral = 10 * 1e8; - await this.OneBtc.registerVault(pubX, pubY, { + const collateral = new BN('10000000000000000000'); // 10e18, 10 ONE + await this.VaultRegistry.registerVault(pubX, pubY, { from: this.vaultId, value: collateral, }); - const vault = await this.OneBtc.vaults(this.vaultId); + const vault = await this.VaultRegistry.vaults(this.vaultId); assert.equal(pubX.toString(), vault.btcPublicKeyX.toString()); assert.equal(pubX.toString(), vault.btcPublicKeyX.toString()); assert.equal(collateral, vault.collateral.toString()); }); - it("Issue 5 BTC", async function () { - const IssueAmount = 5 * 1e8; + it("Issue 0.5 BTC", async function () { + const IssueAmount = new BN('50000000'); // 0.5e8, 0.5 OneBtc const IssueReq = await this.OneBtc.requestIssue(IssueAmount, this.vaultId, { from: this.issueRequester, value: IssueAmount, }); const IssueEvent = IssueReq.logs.filter( - (log) => log.event == "IssueRequest" + (log) => log.event == "IssueRequested" )[0]; const issueId = IssueEvent.args.issueId; const btcAddress = IssueEvent.args.btcAddress; @@ -79,29 +95,40 @@ contract("Redeem unit test", (accounts) => { const heightAndIndex = (btcBlockNumberMock << 32) | btcTxIndexMock; const headerMock = Buffer.alloc(0); const proofMock = Buffer.alloc(0); + const ouputIndexMock = 0; const ExecuteReq = await this.OneBtc.executeIssue( this.issueRequester, issueId, proofMock, btcTx.toBuffer(), heightAndIndex, - headerMock + headerMock, + ouputIndexMock ); + const ExecuteReqEvent = ExecuteReq.logs.filter( + (log) => log.event == "IssueCompleted" + )[0]; + + const fee = IssueAmount / 1000 * 2; // 0.2% fee + const requesterAmount = await this.OneBtc.balanceOf(this.issueRequester); + assert.equal(this.issueRequester, ExecuteReqEvent.args.requester); + assert.equal(requesterAmount.toString(), ExecuteReqEvent.args.amount.toString()); + assert.equal(fee.toString(), ExecuteReqEvent.args.fee.toString()); }); - it("Redeem 1 BTC", async function () { - // Transfer 1 OneBTC - const RedeemAmount = 1 * 1e8; + it("Redeem 0.1 BTC", async function () { + // Transfer 0.1 OneBTC + const RedeemAmount = new BN('1000000'); // 0.1e8, 0.1 OneBtc await this.OneBtc.transfer(this.redeemRequester, RedeemAmount, { from: this.issueRequester }); - // Redeem 1 OneBTC + // Redeem 0.1 OneBTC const beforeOneBtcBalanceVault = await this.OneBtc.balanceOf(this.vaultId); const RedeemReq = await this.OneBtc.requestRedeem(RedeemAmount, this.redeemBtcAddress, this.vaultId, { from: this.redeemRequester }); const RedeemEvent = RedeemReq.logs.filter( - (log) => log.event == "RedeemRequest" + (log) => log.event == "RedeemRequested" )[0]; const redeemId = RedeemEvent.args.redeemId; const amountBtc = RedeemEvent.args.amount; @@ -113,7 +140,7 @@ contract("Redeem unit test", (accounts) => { btcTx = issueTxMock(redeemId, btcBase58, Number(amountBtc)); btcBlockNumberMock = 1000; btcTxIndexMock = 2; - heightAndIndex = (btcBlockNumberMock << 32) | btcTxIndexMock; + btcTxHeightMock = btcBlockNumberMock << 32; headerMock = Buffer.alloc(0); proofMock = Buffer.alloc(0); const ExecuteReq = await this.OneBtc.executeRedeem( @@ -121,12 +148,13 @@ contract("Redeem unit test", (accounts) => { redeemId, proofMock, btcTx.toBuffer(), - heightAndIndex, + btcTxHeightMock, + btcTxIndexMock, headerMock ); const ExecuteEvent = ExecuteReq.logs.filter( - (log) => log.event == "RedeemComplete" + (log) => log.event == "RedeemCompleted" )[0]; this.OneBtcBalanceVault = await this.OneBtc.balanceOf(this.vaultId); assert.equal(this.OneBtcBalanceVault.toString(), (Number(beforeOneBtcBalanceVault) + Number(ExecuteEvent.args.fee)).toString()); @@ -138,40 +166,41 @@ contract("Redeem unit test", (accounts) => { redeemId, proofMock, btcTx.toBuffer(), - heightAndIndex, + btcTxHeightMock, + btcTxIndexMock, headerMock - ), 'request is completed'); + ), 'Request is already completed'); // should not cancel the request which has been already completed await expectRevert(this.OneBtc.cancelRedeem( this.redeemRequester, redeemId - ), 'request is completed'); + ), 'Request is already completed'); }); it("Error on cancelRedeem with the invalid cancel period", async function () { - // Transfer 1 OneBTC - const RedeemAmount = 1 * 1e8; + // Transfer 0.1 OneBTC + const RedeemAmount = new BN('1000000'); // 0.1e8, 0.1 OneBtc await this.OneBtc.transfer(this.redeemRequester, RedeemAmount, { from: this.issueRequester }); - // Redeem 1 OneBTC + // Redeem 0.1 OneBTC const RedeemReq = await this.OneBtc.requestRedeem(RedeemAmount, this.redeemBtcAddress, this.vaultId, { from: this.redeemRequester }); const RedeemEvent = RedeemReq.logs.filter( - (log) => log.event == "RedeemRequest" + (log) => log.event == "RedeemRequested" )[0]; const redeemId = RedeemEvent.args.redeemId; await expectRevert(this.OneBtc.cancelRedeem( this.redeemRequester, redeemId, - ), 'TimeNotExpired'); + ), 'Time not expired'); }); it("Cancel Redeem", async function () { // Transfer 1 OneBTC - const RedeemAmount = 1 * 1e8; + const RedeemAmount = new BN('10000000'); // 0.1e8, 0.1 OneBtc await this.OneBtc.transfer(this.redeemRequester, RedeemAmount, { from: this.issueRequester }); // Redeem 1 OneBTC @@ -179,7 +208,7 @@ contract("Redeem unit test", (accounts) => { from: this.redeemRequester }); const RedeemEvent = RedeemReq.logs.filter( - (log) => log.event == "RedeemRequest" + (log) => log.event == "RedeemRequested" )[0]; const redeemId = RedeemEvent.args.redeemId; @@ -189,7 +218,7 @@ contract("Redeem unit test", (accounts) => { const CancelReq = await this.OneBtc.cancelRedeem(this.redeemRequester, redeemId); const CancelEvent = CancelReq.logs.filter( - (log) => log.event == "RedeemCancel" + (log) => log.event == "RedeemCanceled" )[0]; assert.equal(CancelEvent.args.redeemId.toString(), redeemId.toString()); }); diff --git a/contract/bridge/test/Replace.js b/contract/bridge/test/Replace.js index b1e23e5..ff843e1 100644 --- a/contract/bridge/test/Replace.js +++ b/contract/bridge/test/Replace.js @@ -1,3 +1,10 @@ +const BN = require("bn.js"); +const { expectRevert } = require("@openzeppelin/test-helpers"); +const { web3 } = require("@openzeppelin/test-helpers/src/setup"); +const { deployProxy } = require("@openzeppelin/truffle-upgrades"); + +const ExchangeRateOracleWrapper = artifacts.require("ExchangeRateOracleWrapper"); +const VaultRegistry = artifacts.require("VaultRegistry"); const OneBtc = artifacts.require("OneBtc"); const RelayMock = artifacts.require("RelayMock"); const { issueTxMock } = require("./mock/btcTxMock"); @@ -5,11 +12,39 @@ const { issueTxMock } = require("./mock/btcTxMock"); const bitcoin = require("bitcoinjs-lib"); const bn = (b) => BigInt(`0x${b.toString("hex")}`); +web3.extend({ + property: "miner", + methods: [ + { + name: "incTime", + call: "evm_increaseTime", + params: 1, + }, + { + name: "mine", + call: "evm_mine", + params: 0, + }, + ], +}); + contract("Replace unit test", (accounts) => { before(async function () { - this.name = "name"; - const IRelay = await RelayMock.new(); - this.OneBtc = await OneBtc.new(IRelay.address); + // get contracts + this.RelayMock = await RelayMock.new(); + this.ExchangeRateOracleWrapper = await deployProxy(ExchangeRateOracleWrapper); + this.VaultRegistry = await deployProxy(VaultRegistry, [this.ExchangeRateOracleWrapper.address]); + this.OneBtc = await deployProxy(OneBtc, [this.RelayMock.address, this.ExchangeRateOracleWrapper.address, this.VaultRegistry.address]); + + // set OneBtc address to VaultRegistry + this.VaultRegistry.updateOneBtcAddress(this.OneBtc.address); + + // set BTC/ONE exchange rate + await this.ExchangeRateOracleWrapper.setExchangeRate(10); // 1 OneBtc = 10 ONE + + // increase time to be enable exchange rate + await web3.miner.incTime(Number(1001)); // MAX_DELAY = 1000 + await web3.miner.mine(); this.vaultId = accounts[1]; this.issueRequester = accounts[2]; @@ -22,11 +57,11 @@ contract("Replace unit test", (accounts) => { const pubY = bn(VaultEcPair.publicKey.slice(33, 65)); const collateral = web3.utils.toWei("10"); - await this.OneBtc.registerVault(pubX, pubY, { + await this.VaultRegistry.registerVault(pubX, pubY, { from: this.vaultId, value: collateral, }); - const vault = await this.OneBtc.vaults(this.vaultId); + const vault = await this.VaultRegistry.vaults(this.vaultId); assert.equal(pubX.toString(), vault.btcPublicKeyX.toString()); assert.equal(pubX.toString(), vault.btcPublicKeyX.toString()); assert.equal(collateral, vault.collateral.toString()); @@ -53,13 +88,15 @@ contract("Replace unit test", (accounts) => { const heightAndIndex = (btcBlockNumberMock << 32) | btcTxIndexMock; const headerMock = Buffer.alloc(0); const proofMock = Buffer.alloc(0); + const ouputIndexMock = 0; await this.OneBtc.executeIssue( this.issueRequester, issueId, proofMock, btcTx.toBuffer(), heightAndIndex, - headerMock + headerMock, + ouputIndexMock ); const OneBtcBalance = await this.OneBtc.balanceOf(this.issueRequester); const OneBtcBalanceVault = await this.OneBtc.balanceOf(this.vaultId); @@ -96,11 +133,11 @@ contract("Replace unit test", (accounts) => { const pubY = bn(VaultEcPair.publicKey.slice(33, 65)); const collateral = web3.utils.toWei("10"); - await this.OneBtc.registerVault(pubX, pubY, { + await this.VaultRegistry.registerVault(pubX, pubY, { from: this.newVaultId, value: collateral, }); - const vault = await this.OneBtc.vaults(this.newVaultId); + const vault = await this.VaultRegistry.vaults(this.newVaultId); assert.equal(pubX.toString(), vault.btcPublicKeyX.toString()); assert.equal(pubX.toString(), vault.btcPublicKeyX.toString()); assert.equal(collateral, vault.collateral.toString()); @@ -148,7 +185,7 @@ contract("Replace unit test", (accounts) => { const btcTx = issueTxMock(replaceId, btcBase58, btcAmount); const btcBlockNumberMock = 1000; const btcTxIndexMock = 2; - const heightAndIndex = (btcBlockNumberMock << 32) | btcTxIndexMock; + const btcTxHeightMock = btcBlockNumberMock << 32; const headerMock = Buffer.alloc(0); const proofMock = Buffer.alloc(0); @@ -156,7 +193,8 @@ contract("Replace unit test", (accounts) => { replaceId, proofMock, btcTx.toBuffer(), - heightAndIndex, + btcTxHeightMock, + btcTxIndexMock, headerMock ); diff --git a/contract/bridge/test/TxValidate.test.js b/contract/bridge/test/TxValidate.test.js index e7b447f..acae6da 100644 --- a/contract/bridge/test/TxValidate.test.js +++ b/contract/bridge/test/TxValidate.test.js @@ -11,11 +11,12 @@ contract("transaction parse test", accounts => { const txValidate = await TxValidate.new(); const receiverAddress = '12L1QTVLowqsRUNht35RyBE3RZzmCZzYF3'; const requestId = 123; + const outputIndex = 0; const requestAmount = 2.5e8; const txMock = issueTxMock(requestId, receiverAddress, requestAmount); const parsedTx = await txUtils.extractTx(txMock.toBuffer()); const receiverAddressHex = '0x'+bitcoin.address.fromBase58Check(receiverAddress).hash.toString('hex'); - const amount = await txValidate.validateTransaction(parsedTx.vouts, requestAmount, receiverAddressHex, requestId); + const amount = await txValidate.validateTransaction(parsedTx.vouts, requestAmount, receiverAddressHex, requestId, outputIndex); assert.equal(Number(amount), requestAmount); }); it("validateTxout with wrong requestId", async () => { @@ -23,55 +24,60 @@ contract("transaction parse test", accounts => { const txValidate = await TxValidate.new(); const receiverAddress = '12L1QTVLowqsRUNht35RyBE3RZzmCZzYF3'; const requestId = 123; + const outputIndex = 0; const requestAmount = 2.5e8; const txMock = issueTxMock(requestId, receiverAddress, requestAmount); const parsedTx = await txUtils.extractTx(txMock.toBuffer()); const receiverAddressHex = '0x'+bitcoin.address.fromBase58Check(receiverAddress).hash.toString('hex'); - await expectRevert(txValidate.validateTransaction(parsedTx.vouts, requestAmount, receiverAddressHex, requestId+1), "InvalidOpReturn"); + await expectRevert(txValidate.validateTransaction(parsedTx.vouts, requestAmount, receiverAddressHex, requestId+1, outputIndex), "Invalid OpReturn"); }); it("validateTxout with less amount", async () => { const txUtils = await TxUtils.new(); const txValidate = await TxValidate.new(); const receiverAddress = '12L1QTVLowqsRUNht35RyBE3RZzmCZzYF3'; const requestId = 123; + const outputIndex = 0; const requestAmount = 2.5e8; const txMock = issueTxMock(requestId, receiverAddress, requestAmount-1); const parsedTx = await txUtils.extractTx(txMock.toBuffer()); const receiverAddressHex = '0x'+bitcoin.address.fromBase58Check(receiverAddress).hash.toString('hex'); - await expectRevert(txValidate.validateTransaction(parsedTx.vouts, requestAmount, receiverAddressHex, requestId), "InsufficientValue"); + await expectRevert(txValidate.validateTransaction(parsedTx.vouts, requestAmount, receiverAddressHex, requestId, outputIndex), "Insufficient BTC value"); }); it("validateTxout with more amount", async () => { const txUtils = await TxUtils.new(); const txValidate = await TxValidate.new(); const receiverAddress = '12L1QTVLowqsRUNht35RyBE3RZzmCZzYF3'; const requestId = 123; + const outputIndex = 0; const requestAmount = 2.5e8; const txMock = issueTxMock(requestId, receiverAddress, requestAmount+10); const parsedTx = await txUtils.extractTx(txMock.toBuffer()); const receiverAddressHex = '0x'+bitcoin.address.fromBase58Check(receiverAddress).hash.toString('hex'); - const amount = await txValidate.validateTransaction(parsedTx.vouts, requestAmount, receiverAddressHex, requestId); + const amount = await txValidate.validateTransaction(parsedTx.vouts, requestAmount, receiverAddressHex, requestId, outputIndex); assert.equal(Number(amount), requestAmount+10); }); it("validateTxout with wrong receiver", async () => { const txUtils = await TxUtils.new(); const txValidate = await TxValidate.new(); const receiverAddress = '12L1QTVLowqsRUNht35RyBE3RZzmCZzYF3'; - const requestId = 123; + const requestId = 0; + const outputIndex = 0; const requestAmount = 2.5e8; const txMock = issueTxMock(requestId, receiverAddress, requestAmount+10); const parsedTx = await txUtils.extractTx(txMock.toBuffer()); const receiverAddressHex = '0x1111111111111111111111111111111111111111'; - await expectRevert(txValidate.validateTransaction(parsedTx.vouts, requestAmount, receiverAddressHex, requestId), "InvalidRecipient"); + await expectRevert(txValidate.validateTransaction(parsedTx.vouts, requestAmount, receiverAddressHex, requestId, outputIndex), "Invalid recipient"); }); it("validateTxout without requestId", async () => { const txUtils = await TxUtils.new(); const txValidate = await TxValidate.new(); const receiverAddress = '12L1QTVLowqsRUNht35RyBE3RZzmCZzYF3'; const requestId = 123; + const outputIndex = 0; const requestAmount = 2.5e8; const txMock = issueTxMock(undefined, receiverAddress, requestAmount+10); const parsedTx = await txUtils.extractTx(txMock.toBuffer()); const receiverAddressHex = '0x'+bitcoin.address.fromBase58Check(receiverAddress).hash.toString('hex'); - await expectRevert(txValidate.validateTransaction(parsedTx.vouts, requestAmount, receiverAddressHex, requestId), "NoOpRetrun"); + await expectRevert(txValidate.validateTransaction(parsedTx.vouts, requestAmount, receiverAddressHex, requestId, outputIndex), "Invalid OpReturn"); }); }); \ No newline at end of file diff --git a/contract/bridge/test/VaultRegistry.js b/contract/bridge/test/VaultRegistry.js index f453847..6e199a7 100644 --- a/contract/bridge/test/VaultRegistry.js +++ b/contract/bridge/test/VaultRegistry.js @@ -1,4 +1,8 @@ -const VaultRegistryTestWrapper = artifacts.require("VaultRegistryTestWrapper"); +const { deployProxy } = require("@openzeppelin/truffle-upgrades"); + +const ExchangeRateOracleWrapper = artifacts.require("ExchangeRateOracleWrapper"); +const VaultRegistryWrapper = artifacts.require("VaultRegistryWrapper"); + const bitcoin = require("bitcoinjs-lib"); const bn = (b) => BigInt(`0x${b.toString("hex")}`); @@ -20,7 +24,11 @@ web3.extend({ contract("VaultRegistry unit test", (accounts) => { before(async function () { - this.VaultRegistry = await VaultRegistryTestWrapper.new(); + this.ExchangeRateOracleWrapper = await deployProxy(ExchangeRateOracleWrapper); + this.VaultRegistry = await deployProxy(VaultRegistryWrapper, [this.ExchangeRateOracleWrapper.address]); + + // set OneBtc address with accounts[0] + await this.VaultRegistry.updateOneBtcAddress(accounts[0]); this.vaultId = accounts[1]; this.issueRequester = accounts[2]; diff --git a/contract/bridge/yarn.lock b/contract/bridge/yarn.lock index a4cbbdf..ab3c926 100644 --- a/contract/bridge/yarn.lock +++ b/contract/bridge/yarn.lock @@ -3741,6 +3741,11 @@ ansi-regex@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -5835,6 +5840,14 @@ cli-spinners@^2.0.0: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.0.tgz#36c7dc98fb6a9a76bd6238ec3f77e2425627e939" integrity sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q== +cli-table@^0.3.1: + version "0.3.9" + resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.9.tgz#c0570b079296608a31761a67d9af9841dbee8300" + integrity sha512-7eA6hFtAZwVx3dWAGoaBqTrzWko5jRUFKpHT64ZHkJpaA3y5wf5NlLjguqTRmqycatJZiwftODYYyGNLbQ7MuA== + dependencies: + colors "1.0.3" + strip-ansi "^6.0.1" + cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -5974,6 +5987,11 @@ colorette@^1.3.0: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== +colors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" + integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= + colors@^1.1.2, colors@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -15424,6 +15442,13 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-bom-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz#e7144398577d51a6bed0fa1994fa05f43fd988ee" @@ -15911,6 +15936,14 @@ trim-right@^1.0.1: resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-2.2.1.tgz#c5bf04a5bbec3fd118be4084461b3a27c4d796bf" integrity sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q== +truffle-contract-size@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/truffle-contract-size/-/truffle-contract-size-2.0.1.tgz#9ec1b078b0b3221cc1612400bd6f5df83c7a3a09" + integrity sha512-AIKPwHPC/1pZwtVjgUcgcK23k6gWxKhn4ZnKLr339uieb94UgAUeIwGUkfc87T+0lqgC6ePY7YhsFeoZK2YEsA== + dependencies: + cli-table "^0.3.1" + yargs "^15.3.1" + truffle@^5.1.66: version "5.4.9" resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.4.9.tgz#67f3c82150d1a96905e03f2a91e369426acfb7ef"