diff --git a/contract/bridge/contracts/OneBtc.sol b/contract/bridge/contracts/OneBtc.sol index 1d1e642..68fd2a7 100644 --- a/contract/bridge/contracts/OneBtc.sol +++ b/contract/bridge/contracts/OneBtc.sol @@ -22,12 +22,12 @@ contract OneBtc is ERC20Upgradeable, Issue, Redeem, Replace { } event ReportVaultTheft(address indexed vaultId); - event VaultDoublePayment( address indexed vaultId, bytes32 leftTxId, bytes32 rightTxId ); + mapping(bytes32 => bool) public theftReports; function initialize(IRelay _relay, IExchangeRateOracle _oracle) @@ -158,47 +158,47 @@ contract OneBtc is ERC20Upgradeable, Issue, Redeem, Replace { ERC20Upgradeable._mint(receiver, amount); } - function requestReplace( - address payable oldVaultId, - uint256 btcAmount, - uint256 griefingCollateral - ) external payable { - require(false, "Feature temporarily disabled"); - // Replace._requestReplace(oldVaultId, btcAmount, griefingCollateral); - } - - function acceptReplace( - address oldVaultId, - address newVaultId, - uint256 btcAmount, - uint256 collateral, - uint256 btcPublicKeyX, - uint256 btcPublicKeyY - ) external payable { - require(false, "Feature temporarily disabled"); - // Replace._acceptReplace( - // oldVaultId, - // newVaultId, - // btcAmount, - // collateral, - // btcPublicKeyX, - // btcPublicKeyY - // ); - } - - function executeReplace( - uint256 replaceId, - bytes calldata merkleProof, - bytes calldata rawTx, // avoid compiler error: stack too deep - //bytes calldata _version, bytes calldata _vin, bytes calldata _vout, bytes calldata _locktime, - uint32 height, - uint256 index, - bytes calldata header - ) external { - require(false, "Feature temporarily disabled"); - // bytes memory _vout = verifyTx(height, index, rawTx, header, merkleProof); - // Replace._executeReplace(replaceId, _vout); - } + // function requestReplace( + // address payable oldVaultId, + // uint256 btcAmount, + // uint256 griefingCollateral + // ) external payable { + // require(false, "Feature temporarily disabled"); + // // Replace._requestReplace(oldVaultId, btcAmount, griefingCollateral); + // } + + // function acceptReplace( + // address oldVaultId, + // address newVaultId, + // uint256 btcAmount, + // uint256 collateral, + // uint256 btcPublicKeyX, + // uint256 btcPublicKeyY + // ) external payable { + // require(false, "Feature temporarily disabled"); + // // Replace._acceptReplace( + // // oldVaultId, + // // newVaultId, + // // btcAmount, + // // collateral, + // // btcPublicKeyX, + // // btcPublicKeyY + // // ); + // } + + // function executeReplace( + // uint256 replaceId, + // bytes calldata merkleProof, + // bytes calldata rawTx, // avoid compiler error: stack too deep + // //bytes calldata _version, bytes calldata _vin, bytes calldata _vout, bytes calldata _locktime, + // uint32 height, + // uint256 index, + // bytes calldata header + // ) external { + // require(false, "Feature temporarily disabled"); + // // bytes memory _vout = verifyTx(height, index, rawTx, header, merkleProof); + // // Replace._executeReplace(replaceId, _vout); + // } /** * @dev Report vault misbehavior by providing fraud proof (malicious bitcoin transaction and the corresponding transaction inclusion proof). Fully slashes the vault. diff --git a/contract/bridge/contracts/Replace.sol b/contract/bridge/contracts/Replace.sol index 113b2e2..8635905 100644 --- a/contract/bridge/contracts/Replace.sol +++ b/contract/bridge/contracts/Replace.sol @@ -57,72 +57,72 @@ abstract contract Replace is VaultRegistry, Request { // return collateralFor(amountBtc).mul(5).div(100); // 5% replace griefing collateral // } - function _requestReplace( - address payable oldVaultId, - uint256 btcAmount, - uint256 griefingCollateral - ) internal { - // require( - // msg.sender == oldVaultId, - // "Sender should be the owner of this Vault" - // ); - - // // The oldVault MUST NOT be banned - // Vault storage oldVault = vaults[oldVaultId]; - // require( - // oldVault.liquidatedCollateral == 0, - // "Cannot replace a banned vault" - // ); - - // uint256 requestableTokens = VaultRegistry.requestableToBeReplacedTokens( - // oldVaultId - // ); - // uint256 toBeReplacedIncrease = MathUpgradeable.min( - // requestableTokens, - // btcAmount - // ); - - // uint256 replaceCollateralIncrease = griefingCollateral; - - // if (btcAmount > 0) { - // replaceCollateralIncrease = VaultRegistry.calculateCollateral( - // griefingCollateral, - // toBeReplacedIncrease, - // btcAmount - // ); - // } - - // ( - // uint256 totalToBeReplaced, - // uint256 totalGriefingCollateral - // ) = VaultRegistry.tryIncreaseToBeReplacedTokens( - // oldVaultId, - // toBeReplacedIncrease, - // replaceCollateralIncrease - // ); - - // // check that total-to-be-replaced is above the minimum. NOTE: this means that even - // // a request with amount=0 is valid, as long the _total_ is above DUST. This might - // // be the case if the vault just wants to increase the griefing collateral, for example. - // uint256 dustValue = 0; - // require(totalToBeReplaced >= dustValue, "Amount below dust amount"); - - // // check that that the total griefing collateral is sufficient to back the total to-be-replaced amount - // require( - // getReplaceGriefingCollateral(totalToBeReplaced) <= - // totalGriefingCollateral, - // "Insufficient collateral" - // ); - - // // Lock the oldVault’s griefing collateral. Note that this directly locks the amount - // ICollateral.lockCollateral(oldVaultId, replaceCollateralIncrease); - - // emit RequestReplace( - // oldVaultId, - // toBeReplacedIncrease, - // replaceCollateralIncrease - // ); - } + // function _requestReplace( + // address payable oldVaultId, + // uint256 btcAmount, + // uint256 griefingCollateral + // ) internal { + // require( + // msg.sender == oldVaultId, + // "Sender should be the owner of this Vault" + // ); + + // // The oldVault MUST NOT be banned + // Vault storage oldVault = vaults[oldVaultId]; + // require( + // oldVault.liquidatedCollateral == 0, + // "Cannot replace a banned vault" + // ); + + // uint256 requestableTokens = VaultRegistry.requestableToBeReplacedTokens( + // oldVaultId + // ); + // uint256 toBeReplacedIncrease = MathUpgradeable.min( + // requestableTokens, + // btcAmount + // ); + + // uint256 replaceCollateralIncrease = griefingCollateral; + + // if (btcAmount > 0) { + // replaceCollateralIncrease = VaultRegistry.calculateCollateral( + // griefingCollateral, + // toBeReplacedIncrease, + // btcAmount + // ); + // } + + // ( + // uint256 totalToBeReplaced, + // uint256 totalGriefingCollateral + // ) = VaultRegistry.tryIncreaseToBeReplacedTokens( + // oldVaultId, + // toBeReplacedIncrease, + // replaceCollateralIncrease + // ); + + // // check that total-to-be-replaced is above the minimum. NOTE: this means that even + // // a request with amount=0 is valid, as long the _total_ is above DUST. This might + // // be the case if the vault just wants to increase the griefing collateral, for example. + // uint256 dustValue = 0; + // require(totalToBeReplaced >= dustValue, "Amount below dust amount"); + + // // check that that the total griefing collateral is sufficient to back the total to-be-replaced amount + // require( + // getReplaceGriefingCollateral(totalToBeReplaced) <= + // totalGriefingCollateral, + // "Insufficient collateral" + // ); + + // // Lock the oldVault’s griefing collateral. Note that this directly locks the amount + // ICollateral.lockCollateral(oldVaultId, replaceCollateralIncrease); + + // emit RequestReplace( + // oldVaultId, + // toBeReplacedIncrease, + // replaceCollateralIncrease + // ); + // } // function _withdrawReplace(address oldVaultId, uint256 btcAmount) internal { // require(msg.sender == oldVaultId, "Sender should be old vault owner"); @@ -138,143 +138,143 @@ abstract contract Replace is VaultRegistry, Request { // emit WithdrawReplace(oldVaultId, withdrawnTokens, toWithdrawCollateral); // } - function _acceptReplace( - address oldVaultId, - address newVaultId, - uint256 btcAmount, - uint256 collateral, - uint256 btcPublicKeyX, - uint256 btcPublicKeyY - ) internal { - // require(msg.sender == newVaultId, "Sender should be new vault owner"); - // require( - // oldVaultId != newVaultId, - // "Old vault must not be equal to new vault" - // ); - - // // 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]; - - // require(oldVault.btcPublicKeyX != 0, "Vault does not exist"); - // require(newVault.btcPublicKeyX != 0, "Vault does not exist"); - - // // decrease old-vault's to-be-replaced tokens - // (uint256 redeemableTokens, uint256 griefingCollateral) = VaultRegistry - // .decreaseToBeReplacedTokens(oldVaultId, btcAmount); - - // // TODO: check amount_btc is above the minimum - // uint256 dustValue = 0; - // require(redeemableTokens >= dustValue, "Amount below dust amount"); - - // // Calculate and lock the new-vault's additional collateral - // uint256 actualNewVaultCollateral = VaultRegistry.calculateCollateral( - // collateral, - // redeemableTokens, - // btcAmount - // ); - - // VaultRegistry.tryDepositCollateral( - // newVaultId, - // actualNewVaultCollateral - // ); - - // // increase old-vault's to-be-redeemed tokens - this should never fail - // VaultRegistry.tryIncreaseToBeRedeemedTokens( - // oldVaultId, - // redeemableTokens - // ); - - // // increase new-vault's to-be-issued tokens - this will fail if there is insufficient collateral - // VaultRegistry.tryIncreaseToBeIssuedTokens(newVaultId, redeemableTokens); - - // uint256 replaceId = uint256( - // keccak256(abi.encodePacked(oldVaultId, blockhash(block.number - 1))) - // ); - - // address btcAddress = VaultRegistry.insertVaultDepositAddress( - // newVaultId, - // btcPublicKeyX, - // btcPublicKeyY, - // replaceId - // ); - - // ReplaceRequest storage replace = replaceRequests[replaceId]; - - // require( - // replace.status == RequestStatus.None, - // "This replace already created" - // ); - - // { - // replace.oldVault = address(uint160(oldVaultId)); - // replace.newVault = address(uint160(newVaultId)); - // replace.amount = redeemableTokens; - // replace.btcAddress = btcAddress; - // replace.collateral = actualNewVaultCollateral; - // replace.griefingCollateral = griefingCollateral; - // replace.period = 2 days; - // replace.btcHeight = 0; - // replace.status = RequestStatus.Pending; - // } - - // emit AcceptReplace( - // replaceId, - // replace.oldVault, - // replace.newVault, - // replace.amount, - // replace.collateral, - // replace.btcAddress - // ); - } - - function _executeReplace(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( - // replace.status == RequestStatus.Pending, - // "Wrong request status" - // ); - - // // NOTE: anyone can call this method provided the proof is correct - // address oldVaultId = replace.oldVault; - // address newVaultId = replace.newVault; - - // uint256 amountTransferred = TxValidate.validateTransaction( - // _vout, - // 0, - // replace.btcAddress, - // replaceId, - // 0 - // ); - - // require( - // amountTransferred >= replace.amount, - // "Transaction contains wrong btc amount" - // ); - - // // decrease old-vault's issued & to-be-redeemed tokens, and - // // change new-vault's to-be-issued tokens to issued tokens - // VaultRegistry.replaceTokens( - // oldVaultId, - // newVaultId, - // replace.amount, - // replace.collateral - // ); - - // // if the old vault has not been liquidated, give it back its griefing collateral - // ICollateral.releaseCollateral(oldVaultId, replace.griefingCollateral); - - // // Emit ExecuteReplace event. - // emit ExecuteReplace(replaceId, oldVaultId, newVaultId); - - // // Remove replace request - // { - // replace.status = RequestStatus.Completed; - // } - } + // function _acceptReplace( + // address oldVaultId, + // address newVaultId, + // uint256 btcAmount, + // uint256 collateral, + // uint256 btcPublicKeyX, + // uint256 btcPublicKeyY + // ) internal { + // require(msg.sender == newVaultId, "Sender should be new vault owner"); + // require( + // oldVaultId != newVaultId, + // "Old vault must not be equal to new vault" + // ); + + // // 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]; + + // require(oldVault.btcPublicKeyX != 0, "Vault does not exist"); + // require(newVault.btcPublicKeyX != 0, "Vault does not exist"); + + // // decrease old-vault's to-be-replaced tokens + // (uint256 redeemableTokens, uint256 griefingCollateral) = VaultRegistry + // .decreaseToBeReplacedTokens(oldVaultId, btcAmount); + + // // TODO: check amount_btc is above the minimum + // uint256 dustValue = 0; + // require(redeemableTokens >= dustValue, "Amount below dust amount"); + + // // Calculate and lock the new-vault's additional collateral + // uint256 actualNewVaultCollateral = VaultRegistry.calculateCollateral( + // collateral, + // redeemableTokens, + // btcAmount + // ); + + // VaultRegistry.tryDepositCollateral( + // newVaultId, + // actualNewVaultCollateral + // ); + + // // increase old-vault's to-be-redeemed tokens - this should never fail + // VaultRegistry.tryIncreaseToBeRedeemedTokens( + // oldVaultId, + // redeemableTokens + // ); + + // // increase new-vault's to-be-issued tokens - this will fail if there is insufficient collateral + // VaultRegistry.tryIncreaseToBeIssuedTokens(newVaultId, redeemableTokens); + + // uint256 replaceId = uint256( + // keccak256(abi.encodePacked(oldVaultId, blockhash(block.number - 1))) + // ); + + // address btcAddress = VaultRegistry.insertVaultDepositAddress( + // newVaultId, + // btcPublicKeyX, + // btcPublicKeyY, + // replaceId + // ); + + // ReplaceRequest storage replace = replaceRequests[replaceId]; + + // require( + // replace.status == RequestStatus.None, + // "This replace already created" + // ); + + // { + // replace.oldVault = address(uint160(oldVaultId)); + // replace.newVault = address(uint160(newVaultId)); + // replace.amount = redeemableTokens; + // replace.btcAddress = btcAddress; + // replace.collateral = actualNewVaultCollateral; + // replace.griefingCollateral = griefingCollateral; + // replace.period = 2 days; + // replace.btcHeight = 0; + // replace.status = RequestStatus.Pending; + // } + + // emit AcceptReplace( + // replaceId, + // replace.oldVault, + // replace.newVault, + // replace.amount, + // replace.collateral, + // replace.btcAddress + // ); + // } + + // function _executeReplace(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( + // replace.status == RequestStatus.Pending, + // "Wrong request status" + // ); + + // // NOTE: anyone can call this method provided the proof is correct + // address oldVaultId = replace.oldVault; + // address newVaultId = replace.newVault; + + // uint256 amountTransferred = TxValidate.validateTransaction( + // _vout, + // 0, + // replace.btcAddress, + // replaceId, + // 0 + // ); + + // require( + // amountTransferred >= replace.amount, + // "Transaction contains wrong btc amount" + // ); + + // // decrease old-vault's issued & to-be-redeemed tokens, and + // // change new-vault's to-be-issued tokens to issued tokens + // VaultRegistry.replaceTokens( + // oldVaultId, + // newVaultId, + // replace.amount, + // replace.collateral + // ); + + // // if the old vault has not been liquidated, give it back its griefing collateral + // ICollateral.releaseCollateral(oldVaultId, replace.griefingCollateral); + + // // Emit ExecuteReplace event. + // emit ExecuteReplace(replaceId, oldVaultId, newVaultId); + + // // Remove replace request + // { + // replace.status = RequestStatus.Completed; + // } + // } uint256[45] private __gap; } diff --git a/contract/bridge/contracts/VaultRegistry.sol b/contract/bridge/contracts/VaultRegistry.sol index 12f98d0..c4358a2 100644 --- a/contract/bridge/contracts/VaultRegistry.sol +++ b/contract/bridge/contracts/VaultRegistry.sol @@ -6,27 +6,19 @@ import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/math/MathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import {ICollateral} from "./Collateral.sol"; -import {BitcoinKeyDerivation} from "./crypto/BitcoinKeyDerivation.sol"; import "./IExchangeRateOracle.sol"; +import "./interface/IVaultRegistry.sol"; +import "./interface/IVaultReward.sol"; +import "./lib/VaultRegistryLib.sol"; -abstract contract VaultRegistry is Initializable, ICollateral { +abstract contract VaultRegistry is Initializable, ICollateral, IVaultRegistry { using SafeMathUpgradeable for uint256; - struct Vault { - uint256 btcPublicKeyX; - uint256 btcPublicKeyY; - uint256 collateral; - uint256 issued; - uint256 toBeIssued; - uint256 toBeRedeemed; - uint256 replaceCollateral; - uint256 toBeReplaced; - uint256 liquidatedCollateral; - mapping(address => bool) depositAddresses; - } - mapping(address => Vault) public vaults; - IExchangeRateOracle oracle; + IExchangeRateOracle public oracle; + // upgrade contract + address public vaultReward; + bool public isSetVaultReward; event RegisterVault( address indexed vaultId, @@ -51,16 +43,17 @@ abstract contract VaultRegistry is Initializable, ICollateral { ); event LiquidateVault(); + modifier onlyVaultReward() { + require(msg.sender == vaultReward, "Only VaultReward"); + _; + } + function registerVault(uint256 btcPublicKeyX, uint256 btcPublicKeyY) external payable { address vaultId = msg.sender; - Vault storage vault = vaults[vaultId]; - require(vault.btcPublicKeyX == 0, "Vault already exist"); - require(btcPublicKeyX != 0 && btcPublicKeyY != 0, "Invalid public key"); - vault.btcPublicKeyX = btcPublicKeyX; - vault.btcPublicKeyY = btcPublicKeyY; + VaultRegistryLib.registerVault(vaults[vaultId], btcPublicKeyX, btcPublicKeyY); lockAdditionalCollateral(); emit RegisterVault(vaultId, msg.value, btcPublicKeyX, btcPublicKeyY); } @@ -69,21 +62,8 @@ abstract contract VaultRegistry is Initializable, ICollateral { internal returns (address) { - Vault storage vault = vaults[vaultId]; - require(vault.btcPublicKeyX != 0, "Vault does not exist"); - address derivedKey = BitcoinKeyDerivation.derivate( - vault.btcPublicKeyX, - vault.btcPublicKeyY, - issueId - ); - - require( - !vault.depositAddresses[derivedKey], - "The btc address is already used" - ); - vault.depositAddresses[derivedKey] = true; - - return derivedKey; + requireVaultExistence(vaults[vaultId].btcPublicKeyX); + return VaultRegistryLib.registerDepositAddress(vaults[vaultId], vaultId, issueId); } function insertVaultDepositAddress( @@ -92,22 +72,8 @@ abstract contract VaultRegistry is Initializable, ICollateral { uint256 btcPublicKeyY, uint256 replaceId ) internal returns (address) { - Vault storage vault = vaults[vaultId]; - require(vault.btcPublicKeyX != 0, "Vault does not exist"); - - address btcAddress = BitcoinKeyDerivation.derivate( - btcPublicKeyX, - btcPublicKeyY, - replaceId - ); - - require( - !vault.depositAddresses[btcAddress], - "The btc address is already used" - ); - vault.depositAddresses[btcAddress] = true; - - return btcAddress; + requireVaultExistence(vaults[vaultId].btcPublicKeyX); + return VaultRegistryLib.insertVaultDepositAddress(vaults[vaultId], btcPublicKeyX, btcPublicKeyY, replaceId); } function updatePublicKey(uint256 btcPublicKeyX, uint256 btcPublicKeyY) @@ -115,26 +81,39 @@ abstract contract VaultRegistry is Initializable, ICollateral { { address vaultId = msg.sender; Vault storage vault = vaults[vaultId]; - require(vault.btcPublicKeyX != 0, "Vault does not exist"); + requireVaultExistence(vault.btcPublicKeyX); vault.btcPublicKeyX = btcPublicKeyX; vault.btcPublicKeyY = btcPublicKeyY; emit VaultPublicKeyUpdate(vaultId, btcPublicKeyX, btcPublicKeyY); } function lockAdditionalCollateral() public payable { - address vaultId = msg.sender; - 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); + _lockAdditionalCollateral(msg.sender, msg.value); + } + + function lockAdditionalCollateralFromVaultReward(address _vaultId) external override payable onlyVaultReward { + require(block.timestamp < IVaultReward(vaultReward).getVaultLockExpireAt(_vaultId), "Vault was expired"); + + _lockAdditionalCollateral(_vaultId, msg.value); + } + + function _lockAdditionalCollateral(address _vaultId, uint256 _lockAmount) private { + _updateVaultAccClaimableRewards(_vaultId); + + Vault storage vault = vaults[_vaultId]; + requireVaultExistence(vault.btcPublicKeyX); + vault.collateral = vault.collateral.add(_lockAmount); + ICollateral.lockCollateral(_vaultId, _lockAmount); } function withdrawCollateral(uint256 amount) external { + require(IVaultReward(vaultReward).getVaultLockExpireAt(msg.sender) < block.timestamp, "Vault lock period is not expired"); + Vault storage vault = vaults[msg.sender]; - require(vault.btcPublicKeyX != 0, "Vault does not exist"); + requireVaultExistence(vault.btcPublicKeyX); // is allowed to withdraw collateral require( - amount < + amount <= getTotalCollateral(msg.sender).sub( collateralForIssued(vault.issued.add(vault.toBeIssued)) ), @@ -144,6 +123,11 @@ abstract contract VaultRegistry is Initializable, ICollateral { ICollateral.releaseCollateral(msg.sender, amount); } + function _updateVaultAccClaimableRewards(address _vaultId) internal { + // update vault accClaimableRewards + IVaultReward(vaultReward).updateVaultAccClaimableRewards(_vaultId); + } + function decreaseToBeIssuedTokens(address vaultId, uint256 amount) internal { @@ -220,93 +204,93 @@ abstract contract VaultRegistry is Initializable, ICollateral { return collateral.mul(numerator).div(denominator); } - function requestableToBeReplacedTokens(address vaultId) - internal - returns (uint256 amount) - { - Vault memory vault = vaults[vaultId]; - require(vault.btcPublicKeyX != 0, "Vault does not exist"); + // function requestableToBeReplacedTokens(address vaultId) + // internal + // returns (uint256 amount) + // { + // Vault memory vault = vaults[vaultId]; + // requireVaultExistence(vault.btcPublicKeyX); - uint256 requestableIncrease = vault.issued.sub(vault.toBeRedeemed).sub( - vault.toBeReplaced - ); + // uint256 requestableIncrease = vault.issued.sub(vault.toBeRedeemed).sub( + // vault.toBeReplaced + // ); - return requestableIncrease; - } + // return requestableIncrease; + // } - function tryIncreaseToBeReplacedTokens( - address vaultId, - uint256 tokens, - uint256 collateral - ) internal returns (uint256, uint256) { - Vault storage vault = vaults[vaultId]; + // function tryIncreaseToBeReplacedTokens( + // address vaultId, + // uint256 tokens, + // uint256 collateral + // ) internal returns (uint256, uint256) { + // Vault storage vault = vaults[vaultId]; - uint256 requestableIncrease = requestableToBeReplacedTokens(vaultId); + // uint256 requestableIncrease = requestableToBeReplacedTokens(vaultId); - require( - tokens <= requestableIncrease, - "Could not increase to-be-replaced tokens because it is more than issued amount" - ); + // require( + // tokens <= requestableIncrease, + // "Could not increase to-be-replaced tokens because it is more than issued amount" + // ); - vault.toBeReplaced = vault.toBeReplaced.add(tokens); - vault.replaceCollateral = vault.replaceCollateral.add(collateral); + // vault.toBeReplaced = vault.toBeReplaced.add(tokens); + // vault.replaceCollateral = vault.replaceCollateral.add(collateral); - emit IncreaseToBeReplacedTokens(vaultId, tokens); + // emit IncreaseToBeReplacedTokens(vaultId, tokens); - return (vault.toBeReplaced, vault.replaceCollateral); - } + // return (vault.toBeReplaced, vault.replaceCollateral); + // } - function decreaseToBeReplacedTokens(address vaultId, uint256 tokens) - internal - returns (uint256, uint256) - { - Vault storage vault = vaults[vaultId]; - require(vault.btcPublicKeyX != 0, "Vault does not exist"); + // function decreaseToBeReplacedTokens(address vaultId, uint256 tokens) + // internal + // returns (uint256, uint256) + // { + // Vault storage vault = vaults[vaultId]; + // requireVaultExistence(vault.btcPublicKeyX); - uint256 usedTokens = MathUpgradeable.min(vault.toBeReplaced, tokens); + // uint256 usedTokens = MathUpgradeable.min(vault.toBeReplaced, tokens); - uint256 calculatedCollateral = calculateCollateral( - vault.replaceCollateral, - usedTokens, - vault.toBeReplaced - ); - uint256 usedCollateral = MathUpgradeable.min( - vault.replaceCollateral, - calculatedCollateral - ); + // uint256 calculatedCollateral = calculateCollateral( + // vault.replaceCollateral, + // usedTokens, + // vault.toBeReplaced + // ); + // uint256 usedCollateral = MathUpgradeable.min( + // vault.replaceCollateral, + // calculatedCollateral + // ); - vault.toBeReplaced = vault.toBeReplaced.sub(usedTokens); - vault.replaceCollateral = vault.replaceCollateral.sub(usedCollateral); + // vault.toBeReplaced = vault.toBeReplaced.sub(usedTokens); + // vault.replaceCollateral = vault.replaceCollateral.sub(usedCollateral); - emit DecreaseToBeReplacedTokens(vaultId, usedTokens); + // emit DecreaseToBeReplacedTokens(vaultId, usedTokens); - return (usedTokens, usedCollateral); - } + // return (usedTokens, usedCollateral); + // } - function replaceTokens( - address oldVaultId, - address newVaultId, - uint256 tokens, - uint256 collateral - ) internal { - Vault storage oldVault = vaults[oldVaultId]; - Vault storage newVault = vaults[newVaultId]; + // function replaceTokens( + // address oldVaultId, + // address newVaultId, + // uint256 tokens, + // uint256 collateral + // ) internal { + // Vault storage oldVault = vaults[oldVaultId]; + // Vault storage newVault = vaults[newVaultId]; - require(oldVault.btcPublicKeyX != 0, "Vault does not exist"); - require(newVault.btcPublicKeyX != 0, "Vault does not exist"); + // requireVaultExistence(oldVault.btcPublicKeyX); + // requireVaultExistence(newVault.btcPublicKeyX); - // TODO: add liquidation functionality - // if old_vault.data.is_liquidated() + // // TODO: add liquidation functionality + // // if old_vault.data.is_liquidated() - oldVault.issued = oldVault.issued.sub(tokens); - newVault.issued = newVault.issued.add(tokens); + // oldVault.issued = oldVault.issued.sub(tokens); + // newVault.issued = newVault.issued.add(tokens); - emit ReplaceTokens(oldVaultId, newVaultId, tokens, collateral); - } + // emit ReplaceTokens(oldVaultId, newVaultId, tokens, collateral); + // } function tryDepositCollateral(address vaultId, uint256 amount) internal { Vault storage vault = vaults[vaultId]; - require(vault.btcPublicKeyX != 0, "Vault does not exist"); + requireVaultExistence(vault.btcPublicKeyX); ICollateral.lockCollateral(vaultId, amount); } @@ -333,6 +317,8 @@ 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 { + _updateVaultAccClaimableRewards(vaultId); + Vault storage vault = vaults[vaultId]; // pay the theft report reward to reporter @@ -393,5 +379,32 @@ abstract contract VaultRegistry is Initializable, ICollateral { // vault.toBeRedeemed = 0; } - uint256[45] private __gap; + function requireVaultExistence(uint256 _vaultBtcPublicKeyX) private { + require(_vaultBtcPublicKeyX != 0, "Vault does not exist"); + } + + function getVault(address _vaultId) external view override returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256) { + Vault memory vault = vaults[_vaultId]; + + return ( + vault.btcPublicKeyX, + vault.btcPublicKeyY, + vault.collateral, + vault.issued, + vault.toBeIssued, + vault.toBeRedeemed, + vault.replaceCollateral, + vault.toBeReplaced, + vault.liquidatedCollateral + ); + } + + function setVaultRewardAddress(address _vaultReward) external { + require(!isSetVaultReward, "VaultReward already set"); + isSetVaultReward = true; + + vaultReward = _vaultReward; + } + + uint256[44] private __gap; } diff --git a/contract/bridge/contracts/VaultReserve.sol b/contract/bridge/contracts/VaultReserve.sol new file mode 100644 index 0000000..06c82c9 --- /dev/null +++ b/contract/bridge/contracts/VaultReserve.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; +import "./interface/IVaultRegistry.sol"; + +contract VaultReserve is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable, PausableUpgradeable { + using SafeMathUpgradeable for uint256; + + uint256 public totalDepositAmount; + uint256 public totalWithdrawalAmount; + + address public vaultReward; + + modifier onlyVaultReward() { + require(msg.sender == vaultReward, "only VaultReward"); + _; + } + + receive() external payable { + totalDepositAmount = totalDepositAmount.add(msg.value); + } + + function initialize() external { + __Ownable_init(); + __ReentrancyGuard_init(); + __Pausable_init(); + } + + function depositReward() public payable { + totalDepositAmount = totalDepositAmount.add(msg.value); + } + + function withdrawReward(address payable _to, uint256 _amount) external onlyVaultReward nonReentrant whenNotPaused { + totalWithdrawalAmount = totalWithdrawalAmount.add(_amount); + + // transfer rewards + (bool sent,) = payable(_to).call{value: _amount}(""); + require(sent, "Failed to send ONE"); + } + + function emergencyWithdraw(address payable _to, uint256 _amount) external onlyOwner { + totalWithdrawalAmount = totalWithdrawalAmount.add(_amount); + + // transfer rewards + (bool sent,) = _to.call{value: _amount}(""); + require(sent, "Failed to send ONE"); + } + + function getReserveAmount() external view returns (uint256) { + return totalDepositAmount.sub(totalWithdrawalAmount); + } + + function setVaultReward(address _vaultReward) external onlyOwner { + vaultReward = _vaultReward; + } +} diff --git a/contract/bridge/contracts/VaultReward.sol b/contract/bridge/contracts/VaultReward.sol new file mode 100644 index 0000000..bc7f33a --- /dev/null +++ b/contract/bridge/contracts/VaultReward.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; +import "./interface/IVaultRegistry.sol"; +import "./interface/IVaultReserve.sol"; + +contract VaultReward is Initializable { + using SafeMathUpgradeable for uint256; + + struct LockedVault { + uint256 lockStartAt; + uint256 lockPeriod; + uint256 lockExpireAt; + uint256 rewardClaimAt; + uint256 accClaimableRewards; + uint256 accRewardPerShare; + uint256 accRewardPerShareUpdatedAt; + uint256 collateralDebt; + } + + struct VaultStaker { + uint256 balance; + uint256 accClaimableRewards; + uint256 rewardDebt; + } + + address public oneBtc; + address public vaultReserve; + mapping(address => LockedVault) public lockedVaults; // Vault -> LockedVault + mapping(address => mapping(address => VaultStaker)) public vaultStakers; // Vault -> User -> VaultStaker + mapping(address => address[]) public userStakedVaultList; // User -> Vault list + + event ExtendVaultLockPeriod(address indexed vaultId, uint256 oldLockPeriod, uint256 newLockPeriod); + event ClaimRewards(address indexed vaultId, uint256 amount, uint256 claimAt); + event ClaimRewardsAndLock(address indexed vaultId, uint256 amount, uint256 claimAt); + event UpdateVaultAccClaimableRewards( + address indexed vaultId, + uint256 oldAccClaimableRewards, + uint256 newAccClaimableRewards, + uint256 updatedAt + ); + + modifier vaultExist(address _vaultId) { + (uint256 btcPublicKeyX,,,,,,,,) = IVaultRegistry(oneBtc).getVault(_vaultId); + require(btcPublicKeyX != 0, "Vault does not exist"); + _; + } + + receive() external payable {} + + function initialize(address _oneBtc, address _vaultReserve) public initializer { + oneBtc = _oneBtc; + vaultReserve = _vaultReserve; + } + + function extendVaultLockPeriod(address _vaultId, uint256 _lockPeriod) external vaultExist(_vaultId) { + require(_vaultId == msg.sender, "Invalid vaultId"); + + // check if the lockPeriod is valid + require(_lockPeriod == 3 || _lockPeriod == 6 || _lockPeriod == 12, "Lock period should be one of 3, 6, 12"); + + // update vault accClaimableRewards + updateVaultAccClaimableRewards(_vaultId); + + // get vault + LockedVault storage vault = lockedVaults[_vaultId]; + + // get the current vault lock period + uint256 oldLockPeriod = vault.lockPeriod; + + // update the vault lock info + uint256 secPerDay = 60 * 60 * 24; + uint256 lockPeriodInSec = secPerDay.mul(30).mul(_lockPeriod); + if (vault.lockExpireAt < block.timestamp) { // new or expired vault + vault.lockStartAt = block.timestamp; + vault.lockPeriod = _lockPeriod; + vault.lockExpireAt = block.timestamp.add(lockPeriodInSec); + vault.rewardClaimAt = block.timestamp; + vault.accClaimableRewards = 0; + vault.accRewardPerShare = 0; + vault.accRewardPerShareUpdatedAt = block.timestamp; + vault.collateralDebt = 0; + } else { // locked vault + vault.lockPeriod = vault.lockPeriod.add(_lockPeriod); + vault.lockExpireAt = vault.lockExpireAt.add(lockPeriodInSec); + } + + emit ExtendVaultLockPeriod(_vaultId, oldLockPeriod, vault.lockPeriod); + } + + function updateVaultAccClaimableRewards(address _vaultId) public { + if (block.timestamp <= lockedVaults[_vaultId].lockExpireAt) { + // get vault + LockedVault storage vault = lockedVaults[_vaultId]; + + // store the old accClaimableRewards + uint256 oldAccClaimableRewards = vault.accClaimableRewards; + + // update the vault info + (uint256 claimableRewards, uint256 rewardClaimAt) = getClaimableRewards(_vaultId); + vault.accClaimableRewards = claimableRewards; + vault.rewardClaimAt = rewardClaimAt; + + emit UpdateVaultAccClaimableRewards(_vaultId, oldAccClaimableRewards, vault.accClaimableRewards, rewardClaimAt); + } + } + + function claimRewards(address payable _vaultId) external { + require(_vaultId == msg.sender, "Invalid vaultId"); + + // claiim rewards + (uint256 claimableRewards, uint256 rewardClaimAt) = _claimRewards(_vaultId, _vaultId); + + emit ClaimRewards(_vaultId, claimableRewards, rewardClaimAt); + } + + function claimRewardsAndLock(address payable _vaultId) external { + require(_vaultId == msg.sender, "Invalid vaultId"); + + // claim rewards + (uint256 claimableRewards, uint256 rewardClaimAt) = _claimRewards(_vaultId, address(this)); + + // lock collateral + IVaultRegistry(oneBtc).lockAdditionalCollateralFromVaultReward{value: claimableRewards}(_vaultId); + + emit ClaimRewardsAndLock(_vaultId, claimableRewards, rewardClaimAt); + } + + function _claimRewards(address _vaultId, address payable _to) internal returns(uint256, uint256) { + // get reward debt + (uint256 claimableRewards, uint256 rewardClaimAt) = getClaimableRewards(_vaultId); + + // update the vault info + LockedVault storage vault = lockedVaults[_vaultId]; + vault.accClaimableRewards = 0; + vault.rewardClaimAt = rewardClaimAt; + + // transfer rewards + IVaultReserve(vaultReserve).withdrawReward(_to, claimableRewards); + + return (claimableRewards, rewardClaimAt); + } + + function getClaimableRewards(address _vaultId) public view vaultExist(_vaultId) returns (uint256 claimableRewards, uint256 rewardClaimAt) { + // get vault collateral + (,, uint256 collateral,,,,,, uint256 liquidatedCollateral) = IVaultRegistry(oneBtc).getVault(_vaultId); + uint256 vaultUsedCollateral = collateral.sub(liquidatedCollateral); + + // get vault + LockedVault memory vault = lockedVaults[_vaultId]; + + // get APR based on the lock period + uint256 lockPeriod = vault.lockPeriod; + uint256 vaultAPR; + if (lockPeriod == 3) { + vaultAPR = 5; + } else if (lockPeriod >= 6) { + vaultAPR = 10; + } else if (lockPeriod >= 12) { + vaultAPR = 15; + } + + // get the elapsed time + uint256 secPerDay = 60 * 60 * 24; + uint256 elapsedSecs; + if (block.timestamp <= vault.lockExpireAt) { + rewardClaimAt = block.timestamp.sub( + (block.timestamp.sub(vault.rewardClaimAt)) //elapsed seconds since the last claim + .mod(secPerDay.mul(14)) // mod by 2 weeks to get remaining seconds in this 2 week period + ); //timestamp of last applicable 2 week period for rewards + elapsedSecs = rewardClaimAt.sub(vault.rewardClaimAt); //end result is a amount in seconds that is a multiple of 2 weeks, and represent the number of 2 week periods since last claim + } else { + rewardClaimAt = vault.lockExpireAt; + elapsedSecs = rewardClaimAt.sub(vault.rewardClaimAt); + } + + // calculate the remaining rewards since the last claim time + uint256 claimableUnit = elapsedSecs.div(secPerDay).div(14); // claim every 14 days + if (claimableUnit == 0) { + claimableRewards = vault.accClaimableRewards; + } else { + claimableRewards = vault.accClaimableRewards.add(vaultUsedCollateral.mul(claimableUnit).mul(14).mul(vaultAPR).div(36500)); + } + } + + function getVaultLockExpireAt(address _vaultId) external view returns (uint256) { + return lockedVaults[_vaultId].lockExpireAt; + } +} diff --git a/contract/bridge/contracts/interface/IVaultRegistry.sol b/contract/bridge/contracts/interface/IVaultRegistry.sol new file mode 100644 index 0000000..5492880 --- /dev/null +++ b/contract/bridge/contracts/interface/IVaultRegistry.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IVaultRegistry { + struct Vault { + uint256 btcPublicKeyX; + uint256 btcPublicKeyY; + uint256 collateral; + uint256 issued; + uint256 toBeIssued; + uint256 toBeRedeemed; + uint256 replaceCollateral; + uint256 toBeReplaced; + uint256 liquidatedCollateral; + mapping(address => bool) depositAddresses; + } + + function getVault(address) external view returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256); + + function lockAdditionalCollateralFromVaultReward(address _vaultId) external payable; +} diff --git a/contract/bridge/contracts/interface/IVaultReserve.sol b/contract/bridge/contracts/interface/IVaultReserve.sol new file mode 100644 index 0000000..9c0ef32 --- /dev/null +++ b/contract/bridge/contracts/interface/IVaultReserve.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IVaultReserve { + function withdrawReward(address payable _to, uint256 _amount) external; +} diff --git a/contract/bridge/contracts/interface/IVaultReward.sol b/contract/bridge/contracts/interface/IVaultReward.sol new file mode 100644 index 0000000..04a73ee --- /dev/null +++ b/contract/bridge/contracts/interface/IVaultReward.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IVaultReward { + function updateVaultAccClaimableRewards(address _vaultId) external; + + function getVaultLockExpireAt(address _vaultId) external view returns (uint256); +} diff --git a/contract/bridge/contracts/lib/VaultRegistryLib.sol b/contract/bridge/contracts/lib/VaultRegistryLib.sol new file mode 100644 index 0000000..124289a --- /dev/null +++ b/contract/bridge/contracts/lib/VaultRegistryLib.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; +import {BitcoinKeyDerivation} from "../crypto/BitcoinKeyDerivation.sol"; +import "../interface/IVaultRegistry.sol"; + +library VaultRegistryLib { + function registerVault(IVaultRegistry.Vault storage vault, uint256 btcPublicKeyX, uint256 btcPublicKeyY) + external + { + require(vault.btcPublicKeyX == 0, "Vault already exist"); + require(btcPublicKeyX != 0 && btcPublicKeyY != 0, "Invalid public key"); + vault.btcPublicKeyX = btcPublicKeyX; + vault.btcPublicKeyY = btcPublicKeyY; + } + + function registerDepositAddress(IVaultRegistry.Vault storage vault, address vaultId, uint256 issueId) + external + returns (address) + { + address derivedKey = BitcoinKeyDerivation.derivate( + vault.btcPublicKeyX, + vault.btcPublicKeyY, + issueId + ); + + require( + !vault.depositAddresses[derivedKey], + "The btc address is already used" + ); + vault.depositAddresses[derivedKey] = true; + + return derivedKey; + } + + function insertVaultDepositAddress( + IVaultRegistry.Vault storage vault, + uint256 btcPublicKeyX, + uint256 btcPublicKeyY, + uint256 replaceId + ) external returns (address) { + address btcAddress = BitcoinKeyDerivation.derivate( + btcPublicKeyX, + btcPublicKeyY, + replaceId + ); + + require( + !vault.depositAddresses[btcAddress], + "The btc address is already used" + ); + vault.depositAddresses[btcAddress] = true; + + return btcAddress; + } +} diff --git a/contract/bridge/contracts/test/VaultRegistryTestWrapper.sol b/contract/bridge/contracts/test/VaultRegistryTestWrapper.sol index 1cf467f..663ea32 100644 --- a/contract/bridge/contracts/test/VaultRegistryTestWrapper.sol +++ b/contract/bridge/contracts/test/VaultRegistryTestWrapper.sol @@ -65,8 +65,8 @@ contract VaultRegistryTestWrapper is VaultRegistry { return getFreeCollateral(vaultId); } - function testRequestableToBeReplacedTokens(address vaultId) public returns (uint256) { - uint requestableTokens = requestableToBeReplacedTokens(vaultId); - emit RequestableToBeReplacedTokens(vaultId, requestableTokens); - } + // function testRequestableToBeReplacedTokens(address vaultId) public returns (uint256) { + // uint requestableTokens = requestableToBeReplacedTokens(vaultId); + // emit RequestableToBeReplacedTokens(vaultId, requestableTokens); + // } } \ No newline at end of file diff --git a/contract/bridge/migrations/3_deploy_onebtc.js b/contract/bridge/migrations/3_deploy_onebtc.js index c1e6d5b..293fb42 100644 --- a/contract/bridge/migrations/3_deploy_onebtc.js +++ b/contract/bridge/migrations/3_deploy_onebtc.js @@ -2,11 +2,16 @@ const { deployProxy } = require('@openzeppelin/truffle-upgrades'); const OneBtc = artifacts.require("OneBtc"); const RelayMock = artifacts.require("RelayMock"); const ExchangeRateOracleWrapper = artifacts.require("ExchangeRateOracleWrapper"); +const VaultRegistryLib = artifacts.require("VaultRegistryLib"); module.exports = async function(deployer) { const IRelay = await RelayMock.deployed(); const IExchangeRateOracleWrapper = await ExchangeRateOracleWrapper.deployed(); + + await deployer.deploy(VaultRegistryLib); + await VaultRegistryLib.deployed(); + await deployer.link(VaultRegistryLib, OneBtc); - const c = await deployProxy(OneBtc, [IRelay.address, IExchangeRateOracleWrapper.address], { deployer } ); - console.log(c.address) + const c = await deployProxy(OneBtc, [IRelay.address, IExchangeRateOracleWrapper.address], { unsafeAllowLinkedLibraries: true, from: deployer } ); + console.log('OneBtc deployed at ', c.address) }; diff --git a/contract/bridge/migrations/4_deploy_vaultreserve.js b/contract/bridge/migrations/4_deploy_vaultreserve.js new file mode 100644 index 0000000..16ab990 --- /dev/null +++ b/contract/bridge/migrations/4_deploy_vaultreserve.js @@ -0,0 +1,7 @@ +const { deployProxy } = require('@openzeppelin/truffle-upgrades'); +const VaultReserve = artifacts.require("VaultReserve"); + +module.exports = async function(deployer) { + const c = await deployProxy(VaultReserve, [], { deployer } ); + console.log('VaultReserve deployed at ', c.address) +}; diff --git a/contract/bridge/migrations/5_deploy_vaultreward.js b/contract/bridge/migrations/5_deploy_vaultreward.js new file mode 100644 index 0000000..56a4d1f --- /dev/null +++ b/contract/bridge/migrations/5_deploy_vaultreward.js @@ -0,0 +1,12 @@ +const { deployProxy } = require('@openzeppelin/truffle-upgrades'); +const OneBtc = artifacts.require("OneBtc"); +const VaultReserve = artifacts.require("VaultReserve"); +const VaultReward = artifacts.require("VaultReward"); + +module.exports = async function(deployer) { + const IOneBtc = await OneBtc.deployed(); + const IVaultReserve = await VaultReserve.deployed(); + + const c = await deployProxy(VaultReward, [IOneBtc.address, IVaultReserve.address], { deployer } ); + console.log('VaultReward deployed at ', c.address) +}; diff --git a/contract/bridge/package.json b/contract/bridge/package.json index ee8df3d..a830546 100644 --- a/contract/bridge/package.json +++ b/contract/bridge/package.json @@ -5,7 +5,7 @@ "@interlay/bitcoin-spv-sol": "^3.2.2", "@openzeppelin/contracts-upgradeable": "3.4.2", "@openzeppelin/test-helpers": "^0.5.11", - "@openzeppelin/truffle-upgrades": "^1.10.0", + "@openzeppelin/truffle-upgrades": "^1.14.0", "@truffle/hdwallet-provider": "^1.2.1", "bip32": "2.0.6", "bip39": "3.0.4", @@ -26,7 +26,7 @@ "@chainlink/contracts": "0.2.2", "@nomiclabs/hardhat-ethers": "^2.0.2", "@openzeppelin/contracts-upgradeable": "3.4.2", - "@openzeppelin/hardhat-upgrades": "^1.10.0", + "@openzeppelin/hardhat-upgrades": "^1.16.0", "bn.js": "^5.2.0", "eslint": "^7.32.0", "eslint-config-airbnb-base": "^14.2.1", diff --git a/contract/bridge/scripts/deploy_onebtc_proxy_upgrade.js b/contract/bridge/scripts/deploy_onebtc_proxy_upgrade.js new file mode 100644 index 0000000..41920ed --- /dev/null +++ b/contract/bridge/scripts/deploy_onebtc_proxy_upgrade.js @@ -0,0 +1,27 @@ +const { ethers, upgrades } = require("hardhat"); + +async function main() { + const VaultRegistryLib = await ethers.getContractFactory("VaultRegistryLib"); + const vaultRegistryLib = await VaultRegistryLib.deploy(); + + console.log('vaultRegistryLib', vaultRegistryLib.address); + + const OneBtc = await ethers.getContractFactory( + "OneBtc", + { + libraries: { + VaultRegistryLib: vaultRegistryLib.address + } + } + ); + const oneBtc = await upgrades.upgradeProxy(process.env.ONE_BTC, OneBtc, { unsafeSkipStorageCheck: true, unsafeAllowLinkedLibraries: true }); + + console.log("OneBtc upgraded:", oneBtc.address); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/contract/bridge/scripts/deploy_set_vault_reward_proxy.js b/contract/bridge/scripts/deploy_set_vault_reward_proxy.js new file mode 100644 index 0000000..f50f412 --- /dev/null +++ b/contract/bridge/scripts/deploy_set_vault_reward_proxy.js @@ -0,0 +1,41 @@ +const { ethers } = require("hardhat"); + +async function main() { + // deploy VaultReward contract + const VaultReward = await ethers.getContractFactory("VaultReward"); + const vaultReward = await upgrades.deployProxy(VaultReward, [process.env.ONE_BTC, process.env.VAULT_RESERVE], { initializer: "initialize" }); + + console.log("VaultReward deployed to:", vaultReward.address); + + // Set VaultReward address to OneBtc + const OneBtc = await ethers.getContractFactory( + "OneBtc", + { + libraries: { + VaultRegistryLib: process.env.VAULT_REGISTRY_LIB + } + } + ); + const oneBtc = await OneBtc.attach( + process.env.ONE_BTC + ); + await oneBtc.setVaultRewardAddress(vaultReward.address); + + console.log('VaultReward contract address is set on OneBtc'); + + // Set VaultReward address to VaultReserve + const VaultReserve = await ethers.getContractFactory("VaultReserve"); + const vaultReserve = await VaultReserve.attach( + process.env.VAULT_RESERVE + ); + await vaultReserve.setVaultReward(vaultReward.address) + + console.log('VaultReward contract address is set on VaultReserve'); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/contract/bridge/scripts/deploy_vaultreserve_proxy.js b/contract/bridge/scripts/deploy_vaultreserve_proxy.js new file mode 100644 index 0000000..d31aeb0 --- /dev/null +++ b/contract/bridge/scripts/deploy_vaultreserve_proxy.js @@ -0,0 +1,16 @@ +const { ethers } = require("hardhat"); + +async function main() { + // deploy VaultReserve contract + const VaultReserve = await ethers.getContractFactory("VaultReserve"); + const vaultReserve = await upgrades.deployProxy(VaultReserve, [], { initializer: "initialize" }); + + console.log("VaultReserve deployed to:", vaultReserve.address); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/contract/bridge/test/VaultReward.js b/contract/bridge/test/VaultReward.js new file mode 100644 index 0000000..c7b67d7 --- /dev/null +++ b/contract/bridge/test/VaultReward.js @@ -0,0 +1,315 @@ +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 OneBtc = artifacts.require("OneBtc"); +const VaultRegistryLib = artifacts.require("VaultRegistryLib"); +const RelayMock = artifacts.require("RelayMock"); +const ExchangeRateOracleWrapper = artifacts.require("ExchangeRateOracleWrapper"); +const VaultReserve = artifacts.require("VaultReserve"); +const VaultReward = artifacts.require("VaultReward"); + +const bitcoin = require("bitcoinjs-lib"); +const { assertion } = require("@openzeppelin/test-helpers/src/expectRevert"); +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, + }, + ], +}); + +async function getBlockTimestamp(tx) { + let blockNumber = await web3.eth.getBlockNumber(); + + return (await web3.eth.getBlock(blockNumber)).timestamp; +} + +contract("VaultReward unit test", (accounts) => { + before(async function () { + const deployer = accounts[0]; + + this.RelayMock = await RelayMock.new(); + this.ExchangeRateOracleWrapper = await deployProxy(ExchangeRateOracleWrapper); + + this.VaultRegistryLib = await VaultRegistryLib.new(); + await deployer.link(this.VaultRegistryLib, OneBtc); + + this.OneBtc = await deployProxy(OneBtc, [this.RelayMock.address, this.ExchangeRateOracleWrapper.address], { unsafeAllowLinkedLibraries: true } ); + this.VaultReserve = await deployProxy(VaultReserve, []); + this.VaultReward = await deployProxy(VaultReward, [this.OneBtc.address, this.VaultReserve.address]); + + // set VaultReward contract address to OneBtc contract + await this.OneBtc.setVaultRewardAddress(this.VaultReward.address); + + // set VaultReward contract address to VaultReserve contract + await this.VaultReserve.setVaultReward(this.VaultReward.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]; + + // register the new vault + this.VaultEcPair = bitcoin.ECPair.makeRandom({ compressed: false }); + const pubX = bn(this.VaultEcPair.publicKey.slice(1, 33)); + const pubY = bn(this.VaultEcPair.publicKey.slice(33, 65)); + this.initialCollateral = web3.utils.toWei("10"); + const req = await this.OneBtc.registerVault(pubX, pubY, { + from: this.vaultId, + value: this.initialCollateral + }); + }); + + it("initialize VaultReward with OneBtc address", async function() { + let oneBtc = await this.VaultReward.oneBtc(); + assert.equal(oneBtc, this.OneBtc.address); + }); + + it("extendVaultLockPeriod", async function() { + // set lock period + let vaultLockPeriod = 3; + + // extend vault lock period + let tx = await this.VaultReward.extendVaultLockPeriod(this.vaultId, vaultLockPeriod, { from: this.vaultId }); + let currentTimestamp = await getBlockTimestamp(tx); + + // check vault info + const { lockStartAt, lockPeriod, lockExpireAt, rewardClaimAt, accClaimableRewards, accRewardPerShare, accRewardPerShareUpdatedAt, collateralDebt } = await this.VaultReward.lockedVaults(this.vaultId); + assert.equal(lockStartAt, currentTimestamp); + assert.equal(lockPeriod, vaultLockPeriod) + assert.equal(lockExpireAt, currentTimestamp + (60*60*24*30*vaultLockPeriod)); + assert.equal(rewardClaimAt, currentTimestamp); + assert.equal(accClaimableRewards, 0); + assert.equal(accRewardPerShare, 0); + assert.equal(accRewardPerShareUpdatedAt, currentTimestamp); + assert.equal(collateralDebt, 0); + }); + + it("Error on withdrawal if the vault lock period is not expired", async function() { + await expectRevert(this.OneBtc.withdrawCollateral(this.initialCollateral, { from: this.vaultId }), 'Vault lock period is not expired'); + }); + + it("Errer on extendVaultLockPeriod with mismatched msg.sender", async function() { + // set lock period + let lockPeriod = 9; + + // extend vault lock period + await expectRevert(this.VaultReward.extendVaultLockPeriod(this.vaultId, lockPeriod), 'Invalid vaultId'); + }); + + it("Errer on extendVaultLockPeriod with invalid lock period", async function() { + // set lock period + let lockPeriod = 9; + + // extend vault lock period + await expectRevert(this.VaultReward.extendVaultLockPeriod(this.vaultId, lockPeriod, { from: this.vaultId }), 'Lock period should be one of 3, 6, 12'); + }); + + it("updateVaultAccClaimableRewards on the second extendVaultLockPeriod", async function() { + // get the current vault info + const { lockStartAt: oldLockStartAt, lockPeriod: oldLockPeriod, lockExpireAt: oldLockExpireAt, rewardClaimAt: oldRewardClaimAt, accClaimableRewards: oldAccClaimableRewards } = await this.VaultReward.lockedVaults(this.vaultId); + + // increase time + await web3.miner.incTime(Number(3600 * 24 * 14)); // 14 day + await web3.miner.mine(); + + // set lock period + let vaultLockPeriod = 3; + + // extend vault lock period + let tx = await this.VaultReward.extendVaultLockPeriod(this.vaultId, vaultLockPeriod, { from: this.vaultId }); + let currentTimestamp = await getBlockTimestamp(tx); + + // get vault info + const { lockStartAt, lockPeriod, lockExpireAt, rewardClaimAt, accClaimableRewards, accRewardPerShare, accRewardPerShareUpdatedAt, collateralDebt } = await this.VaultReward.lockedVaults(this.vaultId); + + // get expectations + const vault = await this.OneBtc.getVault(this.vaultId); + let vaultUsedCollateral = Number(vault[2]) - Number(vault[8]); + let accClaimableRewardsExpectation = vaultUsedCollateral * 5 * 14 / 365 / 100; // lockPeriod: 3 months, APR: 5% + + // check vault info + assert.equal(Number(lockStartAt), Number(oldLockStartAt)); + assert.equal(Number(lockPeriod), Number(oldLockPeriod) + Number(vaultLockPeriod)) + assert.equal(Number(lockExpireAt), Number(oldLockExpireAt) + (60*60*24*30*vaultLockPeriod)); + assert.closeTo(Number(rewardClaimAt), currentTimestamp, 1); + assert.closeTo(Number(accClaimableRewards), accClaimableRewardsExpectation, 10); + }); + + it("getClaimableRewards", async function() { + // get vault info + const { lockStartAt, lockPeriod, lockExpireAt, rewardClaimAt, collateralUpdatedAt, accClaimableRewards } = await this.VaultReward.lockedVaults(this.vaultId); + + // increase time + await web3.miner.incTime(Number(3600 * 24 * 20)); // 20 day + await web3.miner.mine(); + + // get expectations + const vault = await this.OneBtc.getVault(this.vaultId); + let vaultUsedCollateral = Number(vault[2]) - Number(vault[8]); + let claimableRewardsExpectation = Number(accClaimableRewards) + (vaultUsedCollateral * 10 * 14 / 365 / 100); // lockPeriod: 6 months, APR: 10%, accRewards: for 14 days, not 20 days + let currentTimestamp = await getBlockTimestamp(vault); + let rewardClaimAtExpectation = Number(currentTimestamp) - (60*60*24*6); // 20-14=6 + + // check vault claimable rewards + const {claimableRewards, rewardClaimAt: claimAt} = await this.VaultReward.getClaimableRewards(this.vaultId); + assert.equal(Number(claimableRewards), claimableRewardsExpectation); + assert.closeTo(Number(claimAt), rewardClaimAtExpectation, 1); + }); + + it("updateVaultAccClaimableRewards on collateral change", async function() { + // get vault info + const { lockStartAt, lockPeriod, lockExpireAt, rewardClaimAt, collateralUpdatedAt, accClaimableRewards } = await this.VaultReward.lockedVaults(this.vaultId); + + let vault = await this.OneBtc.getVault(this.vaultId); + let vaultUsedCollateral = Number(vault[2]) - Number(vault[8]); + + // increase time + await web3.miner.incTime(Number(3600 * 24 * 20)); // 20 day + await web3.miner.mine(); + + // lock the additional collateral + let lockAmount = web3.utils.toWei("5"); + let tx = await this.OneBtc.lockAdditionalCollateral({ from: this.vaultId, value: lockAmount }); + + // get expectations + let claimableRewardsExpectation = Number(accClaimableRewards) + (vaultUsedCollateral * 10 * 28 / 365 / 100); // lockPeriod: 6 months, APR: 10%, accRewards: for 28 days, not 40(20+20) days + let currentTimestamp = await getBlockTimestamp(tx); + let rewardClaimAtExpectation = Number(currentTimestamp) - (60*60*24*12); // 40-28=12 + + // check vault claimable rewards + const {claimableRewards, rewardClaimAt: claimAt} = await this.VaultReward.getClaimableRewards(this.vaultId); + assert.equal(Number(claimableRewards), claimableRewardsExpectation); + assert.closeTo(Number(claimAt), rewardClaimAtExpectation, 1); + + // increase time + await web3.miner.incTime(Number(3600 * 24 * 20)); // 20 day + await web3.miner.mine(); + + // get the current vault collateral amount + vault = await this.OneBtc.getVault(this.vaultId); + let newVaultUsedCollateral = Number(vault[2]) - Number(vault[8]); + + // get expectations + claimableRewardsExpectation = Number(accClaimableRewards) + (vaultUsedCollateral * 10 * 28 / 365 / 100) + (newVaultUsedCollateral * 10 * 28 / 365 / 100); // lockPeriod: 6 months, APR: 10%, accRewards: for 56 days (old Collateral:28 days + new collateral: 28 days), not 60(20+20+20) days + currentTimestamp = await getBlockTimestamp(tx); + rewardClaimAtExpectation = Number(currentTimestamp) - (60*60*24*4); // 60-56=4 + + // check vault claimable rewards + const {claimableRewards: newClaimableRewards, rewardClaimAt: newClaimAt} = await this.VaultReward.getClaimableRewards(this.vaultId); + assert.closeTo(Number(newClaimableRewards), claimableRewardsExpectation, 100); + assert.closeTo(Number(newClaimAt), rewardClaimAtExpectation, 1); + }); + + it("claimRewards", async function() { + // check old vault balance + let oldVaultBalance = await web3.eth.getBalance(this.vaultId); + + // get vault claimable rewards + const {claimableRewards, rewardClaimAt: claimAt} = await this.VaultReward.getClaimableRewards(this.vaultId); + + // deposit rewards to VaultReserve contract + await this.VaultReserve.depositReward({ + from: accounts[0], + value: Number(claimableRewards) * 2 + }); + + // claim rewards + const receipt = await this.VaultReward.claimRewards(this.vaultId, { from: this.vaultId }); + const gasUsed = receipt.receipt.gasUsed; + const tx = await web3.eth.getTransaction(receipt.tx); + const gasPrice = tx.gasPrice; + const gas = gasPrice * gasUsed; + + // check new vault balance + let newVaultBalance = await web3.eth.getBalance(this.vaultId); + assert.closeTo(Number(oldVaultBalance) + Number(claimableRewards) - Number(gas), Number(newVaultBalance), 100000); + }); + + it("claimRewardsAndLock", async function() { + // get old vault balance + let oldVaultBalance = await web3.eth.getBalance(this.vaultId); + + // check old vault collateral + let vault = await this.OneBtc.getVault(this.vaultId); + let oldVaultCollateral = Number(vault[2]) - Number(vault[8]); + + // increase time + await web3.miner.incTime(Number(3600 * 24 * 20)); // 20 day + await web3.miner.mine(); + + // get vault claimable rewards + const {claimableRewards, rewardClaimAt: claimAt} = await this.VaultReward.getClaimableRewards(this.vaultId); + + // deposit rewards to VaultReserve contract + await this.VaultReserve.depositReward({ + from: accounts[0], + value: Number(claimableRewards) * 2 + }); + + // claim rewards and lock it again + const receipt = await this.VaultReward.claimRewardsAndLock(this.vaultId, { from: this.vaultId }); + const gasUsed = receipt.receipt.gasUsed; + const tx = await web3.eth.getTransaction(receipt.tx); + const gasPrice = tx.gasPrice; + const gas = gasPrice * gasUsed; + + // get new vault balance + let newVaultBalance = await web3.eth.getBalance(this.vaultId); + + // get new vault collateral + vault = await this.OneBtc.getVault(this.vaultId); + let newVaultCollateral = Number(vault[2]) - Number(vault[8]); + + // check new vault balance and collateral + assert.closeTo(Number(oldVaultBalance) - Number(gas), Number(newVaultBalance), 100000); + assert.closeTo(Number(oldVaultCollateral) + Number(claimableRewards), Number(newVaultCollateral), 100000); + }); + + it("withdraw if the vault lock period is expired", async function() { + // get old vault balance + let oldVaultBalance = await web3.eth.getBalance(this.vaultId); + + // increase time + await web3.miner.incTime(Number(3600 * 24 * 30 * 12)); // 1 year + await web3.miner.mine(); + + // check old vault collateral + let vault = await this.OneBtc.getVault(this.vaultId); + let oldVaultCollateral = vault[2].sub(vault[8]); + + // withdraw all collateral and claimable rewards + const receipt = await this.OneBtc.withdrawCollateral(oldVaultCollateral, { from: this.vaultId }); + const gasUsed = receipt.receipt.gasUsed; + const tx = await web3.eth.getTransaction(receipt.tx); + const gasPrice = tx.gasPrice; + const gas = gasPrice * gasUsed; + + // get new vault balance + let newVaultBalance = await web3.eth.getBalance(this.vaultId); + + // get new vault collateral + vault = await this.OneBtc.getVault(this.vaultId); + let newVaultCollateral = vault[2].sub(vault[8]); + + // check new vault balance and collateral + assert.closeTo(Number(oldVaultBalance) + Number(oldVaultCollateral) - Number(gas), Number(newVaultBalance), 100000); + assert.equal(Number(newVaultCollateral), 0); + }); +}); diff --git a/contract/bridge/yarn.lock b/contract/bridge/yarn.lock index a4cbbdf..2f7cb7f 100644 --- a/contract/bridge/yarn.lock +++ b/contract/bridge/yarn.lock @@ -2119,12 +2119,14 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-3.4.2.tgz#2c2a1b0fa748235a1f495b6489349776365c51b3" integrity sha512-mDlBS17ymb2wpaLcrqRYdnBAmP1EwqhOXMvqWk2c5Q1N1pm5TkiCtXM9Xzznh4bYsQBq0aIWEkFFE2+iLSN1Tw== -"@openzeppelin/hardhat-upgrades@^1.10.0": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-1.10.0.tgz#e7751e3b9a005ccc9cef4e0de190628b181b59b6" - integrity sha512-iGe058iV7Ba/g11RxlbqBG47nbqbZn1FRdg1FCQq7xPmvjRhXmFsoI/5gGw5el0aZlLDRtpFOBZbzMZvI/S7iw== +"@openzeppelin/hardhat-upgrades@^1.16.0": + version "1.17.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-1.17.0.tgz#24ea0f366c3b2df985263cf8b1b796afd04d7e13" + integrity sha512-GNxR3/3fCKQsFpBi/r+5ib6U81UM9KCypmcOQxuCkVp9JKJ80/3hQdg1R+AQku/dlnhutPsfkCokH2LZFc5mNA== dependencies: - "@openzeppelin/upgrades-core" "^1.9.0" + "@openzeppelin/upgrades-core" "^1.14.1" + chalk "^4.1.0" + proper-lockfile "^4.1.1" "@openzeppelin/test-helpers@^0.5.11": version "0.5.13" @@ -2142,38 +2144,25 @@ web3 "^1.2.5" web3-utils "^1.2.5" -"@openzeppelin/truffle-upgrades@^1.10.0": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/truffle-upgrades/-/truffle-upgrades-1.10.0.tgz#f4d575889a417bae576a14f9738cc425e423f15c" - integrity sha512-ed63wI8inKCKXyPVjxgtlKMDhd5MY/5TfaGZJLwx23QHbkj7IBVG7t/aKaj4hea47/YuPCZPgedm8/EJ+l98yw== +"@openzeppelin/truffle-upgrades@^1.14.0": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/truffle-upgrades/-/truffle-upgrades-1.15.0.tgz#eb562ab60f6af291b9b510af52ceb0ecc5c9a25f" + integrity sha512-5ihLqw0c9MKWQqj1J7qiBYgERu27ZwEjK1/fSicl2BGwwTyRK3Zs7sStOl7yWnCayCFPJPMtWGVwd6UetcUApQ== dependencies: - "@openzeppelin/upgrades-core" "^1.10.0" + "@openzeppelin/upgrades-core" "^1.14.1" "@truffle/contract" "^4.3.26" - solidity-ast "^0.4.15" - -"@openzeppelin/upgrades-core@^1.10.0": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.10.0.tgz#d3aa72b7a23827e0e6daff08ddfb8dcd75171abb" - integrity sha512-N20t1i1wlHrVmu3etVZLiaRxT6XLkCrO9gIo4mUZNpsaVftl8V+WBu8o940AjoYXvzTEqj7O0re2DSFzjpkRBw== - dependencies: - bn.js "^5.1.2" - cbor "^8.0.0" chalk "^4.1.0" - compare-versions "^3.6.0" - debug "^4.1.1" - ethereumjs-util "^7.0.3" - proper-lockfile "^4.1.1" solidity-ast "^0.4.15" -"@openzeppelin/upgrades-core@^1.9.0": - version "1.9.2" - resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.9.2.tgz#9d7497f58b1f2bb704c162c716302bfff7923749" - integrity sha512-LU2NMvnz+6jXheh3Rnfql4UgtS7ViHWwcivS3JRI9DMCazmlyibwMYz5QMakrNNGAF7bY0t0Sw1UCfe5qTYxjA== +"@openzeppelin/upgrades-core@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.14.1.tgz#a0e1c83f9811186ac49d286e6b43ae129097422b" + integrity sha512-iKlh1mbUxyfdjdEiUFyhMkqirfas+DMUu7ED53nZbHEyhcYsm+5Fl/g0Bv6bZA+a7k8kO8+22DNEKsqaDUBc2Q== dependencies: bn.js "^5.1.2" cbor "^8.0.0" chalk "^4.1.0" - compare-versions "^3.6.0" + compare-versions "^4.0.0" debug "^4.1.1" ethereumjs-util "^7.0.3" proper-lockfile "^4.1.1" @@ -5835,6 +5824,13 @@ 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.11" + resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.11.tgz#ac69cdecbe81dccdba4889b9a18b7da312a9d3ee" + integrity sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ== + dependencies: + colors "1.0.3" + cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -5974,6 +5970,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" @@ -6015,10 +6016,10 @@ commander@^2.15.0, commander@^2.20.3, commander@^2.9.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -compare-versions@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" - integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== +compare-versions@^4.0.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-4.1.3.tgz#8f7b8966aef7dc4282b45dfa6be98434fc18a1a4" + integrity sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg== component-emitter@1.2.1: version "1.2.1" @@ -15911,6 +15912,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"