diff --git a/contracts/Airdrop.sol b/contracts/Airdrop.sol new file mode 100644 index 0000000..0d5b571 --- /dev/null +++ b/contracts/Airdrop.sol @@ -0,0 +1,168 @@ +/* solhint-disable-next-line compiler-fixed */ +pragma solidity ^0.4.17; + +// Copyright 2018 OpenST Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ---------------------------------------------------------------------------- +// Utility chain: Airdrop +// +// http://www.simpletoken.org/ +// +// ---------------------------------------------------------------------------- + +import "./Workers.sol"; +import "./Pricer.sol"; + + +contract Airdrop is Pricer { + + /* + * Events + */ + /// Emit AirdropPayment Event + event AirdropPayment( + address indexed _beneficiary, + uint256 _tokenAmount, + address indexed _commissionBeneficiary, + uint256 _commissionTokenAmount, + bytes3 _currency, + uint256 _actualPricePoint, + address indexed _spender, + uint256 _airdropAmount + ); + + /* + * Storage + */ + Workers public workers; + address public airdropBudgetHolder; + + /* + * Constructor + */ + /// @dev Takes _brandedToken, _baseCurrency, _workers, _airdropBudgetHolder; + /// constructor; + /// public method; + /// @param _brandedToken Branded Token + /// @param _baseCurrency Base Currency + /// @param _workers Workers contract address + /// @param _airdropBudgetHolder Airdrop Budget Holder Address + function Airdrop( + address _brandedToken, + bytes3 _baseCurrency, + Workers _workers, + address _airdropBudgetHolder) + public + Pricer(_brandedToken, _baseCurrency) + OpsManaged() + { + require(_workers != address(0)); + require(airdropBudgetHolder != address(0)); + + workers = _workers; + airdropBudgetHolder = _airdropBudgetHolder; + } + + /* + * External functions + */ + /// payAirdrop matches the behaviour of Pricer:pay with extra functionality of airdrop evaluation + /// @param _beneficiary beneficiary + /// @param _transferAmount transferAmount + /// @param _commissionBeneficiary commissionBeneficiary + /// @param _commissionAmount commissionAmount + /// @param _currency currency + /// @param _intendedPricePoint intendedPricePoint + /// @param _spender spender + /// @param _airdropAmount airdropAmount + /// @return uint256 totalPaid + function payAirdrop( + address _beneficiary, + uint256 _transferAmount, + address _commissionBeneficiary, + uint256 _commissionAmount, + bytes3 _currency, + uint256 _intendedPricePoint, + address _spender, + uint256 _airdropAmount) + public + returns ( + uint256 /* totalPaid */) + { + require(workers.isWorker(msg.sender)); + require(_spender != address(0)); + + require(isValidBeneficiaryData(_beneficiary, _transferAmount, + _commissionBeneficiary, _commissionAmount)); + + uint256 tokenAmount = _transferAmount; + uint256 commissionTokenAmount = _commissionAmount; + uint256 pricePoint = _intendedPricePoint; + + // check Margin And Calculate BTAmount + if (_currency != "") { + (pricePoint, tokenAmount, commissionTokenAmount) = validateMarginAndCalculateBTAmount(_currency, + _intendedPricePoint, _transferAmount, _commissionAmount); + } + + require(performAirdropTransferToSpender(_spender, _airdropAmount, + tokenAmount, commissionTokenAmount)); + require(performTransfers(_spender, _beneficiary, tokenAmount, + _commissionBeneficiary, commissionTokenAmount)); + + /// Emit AirdropPayment Event + AirdropPayment(_beneficiary, tokenAmount, _commissionBeneficiary, + commissionTokenAmount, _currency, pricePoint, _spender, _airdropAmount); + + return ((tokenAmount + commissionTokenAmount)); + } + + /* + * Private functions + */ + /// @dev Takes _spender, _airdropAmount, _tokenAmount, _commissionTokenAmount; + /// Calculate airdropUsed to transfer + /// Perform perform Airdrop Transfer To Spender + /// internal method; + /// @param _spender spenderUser + /// @param _airdropAmount airdropAmount + /// @param _tokenAmount tokenAmount + /// @param _commissionTokenAmount commissionTokenAmount + /// @return uint256 airdropUsed + function performAirdropTransferToSpender( + address _spender, + uint256 _airdropAmount, + uint256 _tokenAmount, + uint256 _commissionTokenAmount) + private + returns ( + bool /* boolean value */) + { + uint256 totalPaid = (_tokenAmount + _commissionTokenAmount); + // Find out minimum of totalPaid and _airdropAmount + uint256 airdropUsed = _airdropAmount; + if (totalPaid < airdropUsed) { + airdropUsed = totalPaid; + } + + // Prefund the user from the airdrop budget holder + if (airdropUsed > 0) { + require(EIP20Interface(brandedToken()).transferFrom(airdropBudgetHolder, _spender, airdropUsed)); + } + + return true; + } + +} \ No newline at end of file diff --git a/contracts/Pricer.sol b/contracts/Pricer.sol index d2bd0b6..3e15c5b 100644 --- a/contracts/Pricer.sol +++ b/contracts/Pricer.sol @@ -1,3 +1,4 @@ +/* solhint-disable-next-line compiler-fixed */ pragma solidity ^0.4.17; // Copyright 2018 OpenST Ltd. @@ -60,8 +61,8 @@ contract Pricer is OpsManaged, PricerInterface { /// @dev Takes _brandedToken, _baseCurrency; /// constructor; /// public method; - /// @param _brandedToken _brandedToken - /// @param _baseCurrency _baseCurrency + /// @param _brandedToken Branded Token + /// @param _baseCurrency Base Currency function Pricer( address _brandedToken, bytes3 _baseCurrency) @@ -76,6 +77,18 @@ contract Pricer is OpsManaged, PricerInterface { pricerConversionRate = UtilityTokenInterface(_brandedToken).conversionRate(); } + /* + * External functions + */ + /// clean up or revoke airdrop contract + function remove() + external + onlyAdminOrOps + { + Removed(msg.sender); + selfdestruct(msg.sender); + } + /// @dev Returns address of the branded token; /// public method; /// @return address @@ -212,7 +225,7 @@ contract Pricer is OpsManaged, PricerInterface { } /// @dev Takes _transferAmount, _commissionAmount, _currency; - /// public method + /// public view method /// @param _transferAmount transferAmount /// @param _commissionAmount commissionAmount /// @param _currency currency @@ -222,6 +235,7 @@ contract Pricer is OpsManaged, PricerInterface { uint256 _commissionAmount, bytes3 _currency) public + view returns ( uint256 pricePoint, uint256 tokenAmount, @@ -251,7 +265,7 @@ contract Pricer is OpsManaged, PricerInterface { /// @param _commissionAmount commissionAmount /// @param _currency currency /// @param _intendedPricePoint _intendedPricePoint - /// @return bool isSuccess + /// @return uint256 total paid function pay( address _beneficiary, uint256 _transferAmount, @@ -260,36 +274,28 @@ contract Pricer is OpsManaged, PricerInterface { bytes3 _currency, uint256 _intendedPricePoint) public - returns (bool /* success */) + returns (uint256 /* total paid */) { - require(_beneficiary != address(0)); - require(_transferAmount != 0); - - if (_commissionAmount > 0) { - require(_commissionBeneficiary != address(0)); - } + require(isValidBeneficiaryData(_beneficiary, _transferAmount, + _commissionBeneficiary, _commissionAmount)); uint256 tokenAmount = _transferAmount; uint256 commissionTokenAmount = _commissionAmount; uint256 pricePoint = _intendedPricePoint; - if (_currency != 0) { - pricePoint = getPricePoint(_currency); - require(pricePoint > 0); - require(isPricePointInRange(_intendedPricePoint, pricePoint, pricerAcceptedMargins[_currency])); - (tokenAmount, commissionTokenAmount) = getBTAmountFromCurrencyValue(pricePoint, - _transferAmount, _commissionAmount); - } - - require(EIP20Interface(pricerBrandedToken).transferFrom(msg.sender, _beneficiary, tokenAmount)); - if (_commissionBeneficiary != address(0)) { - require(EIP20Interface(pricerBrandedToken).transferFrom(msg.sender, - _commissionBeneficiary, commissionTokenAmount)); + + // check Margin And Calculate BTAmount + if (_currency != "") { + (pricePoint, tokenAmount, commissionTokenAmount) = validateMarginAndCalculateBTAmount(_currency, + _intendedPricePoint, _transferAmount, _commissionAmount); } - + + require(performTransfers(msg.sender, _beneficiary, tokenAmount, + _commissionBeneficiary, commissionTokenAmount)); + //Trigger Event for PaymentComplete - Payment(_beneficiary, _transferAmount, _commissionBeneficiary, - _commissionAmount, _currency, _intendedPricePoint, pricePoint); - return true; + Payment(_beneficiary, tokenAmount, _commissionBeneficiary, + commissionTokenAmount, _currency, _intendedPricePoint, pricePoint); + return (tokenAmount + commissionTokenAmount); } /// @dev Takes _currency; @@ -299,17 +305,21 @@ contract Pricer is OpsManaged, PricerInterface { /// @return (pricePoint) function getPricePoint( bytes3 _currency) - public + public + view returns (uint256) /* pricePoint */ { PriceOracleInterface currentPriceOracle = pricerPriceOracles[_currency]; require(currentPriceOracle != address(0)); return (currentPriceOracle.getPrice()); } - + + /* + * Internal functions + */ /// @dev Takes _intendedPricePoint, _currentPricePoint, _acceptedMargin; /// checks if the current price point is in the acceptable range of intendedPricePoint; - /// private method; + /// internal method; /// @param _intendedPricePoint intendedPricePoint /// @param _currentPricePoint currentPricePoint /// @param _acceptedMargin acceptedMargin @@ -318,7 +328,7 @@ contract Pricer is OpsManaged, PricerInterface { uint256 _intendedPricePoint, uint256 _currentPricePoint, uint256 _acceptedMargin) - private + internal pure returns (bool /*isValid*/) { @@ -334,7 +344,7 @@ contract Pricer is OpsManaged, PricerInterface { /// @dev Takes _pricePoint, _transferAmount, _commissionAmount; /// calculates the number of branded token equivalant to the currency amount; - /// private method; + /// internal method; /// @param _pricePoint pricePoint /// @param _transferAmount transferAmount /// @param _commissionAmount commissionAmount @@ -343,7 +353,7 @@ contract Pricer is OpsManaged, PricerInterface { uint256 _pricePoint, uint256 _transferAmount, uint256 _commissionAmount) - private + internal view returns (uint256, uint256) /* number of BT ,number of commission BT */ { @@ -352,4 +362,88 @@ contract Pricer is OpsManaged, PricerInterface { uint256 commissionAmountBT = SafeMath.div(SafeMath.mul(_commissionAmount, adjConversionRate), _pricePoint); return (amountBT, commissionAmountBT); } + + /// @dev Takes _beneficiary, _transferAmount, _commissionBeneficiary, _commissionAmount; + /// checks if the current price point is in the acceptable range of intendedPricePoint; + /// internal method; + /// @param _beneficiary beneficiary + /// @param _transferAmount transferAmount + /// @param _commissionBeneficiary commissionBeneficiary + /// @param _commissionAmount commissionAmount + /// @return bool isValid + function isValidBeneficiaryData( + address _beneficiary, + uint256 _transferAmount, + address _commissionBeneficiary, + uint256 _commissionAmount) + internal + returns (bool /*isValid*/) + { + require(_beneficiary != address(0)); + require(_transferAmount != 0); + + if (_commissionAmount > 0) { + require(_commissionBeneficiary != address(0)); + } + return true; + } + + /// @dev Takes _spender, _beneficiary, _tokenAmount, _commissionBeneficiary, _commissionTokenAmount; + /// Perform tokenAmount transfer + /// Perform commissionTokenAmount transfer + /// internal method; + /// @param _spender spender + /// @param _beneficiary beneficiary + /// @param _tokenAmount tokenAmount + /// @param _commissionBeneficiary commissionBeneficiary + /// @param _commissionTokenAmount commissionTokenAmount + /// @return (bool) + function performTransfers( + address _spender, + address _beneficiary, + uint256 _tokenAmount, + address _commissionBeneficiary, + uint256 _commissionTokenAmount) + internal + returns ( + bool /* boolean value */) + { + require(EIP20Interface(pricerBrandedToken).transferFrom(_spender, _beneficiary, _tokenAmount)); + + if (_commissionBeneficiary != address(0)) { + require(EIP20Interface(pricerBrandedToken).transferFrom(_spender, + _commissionBeneficiary, _commissionTokenAmount)); + } + return true; + } + + /// @dev Takes _currency, _intendedPricePoint, _transferAmount, _commissionAmount; + /// Validate accepted margin + /// Calculates tokenAmount and commissionTokenAmount + /// internal method + /// @param _currency currency + /// @param _intendedPricePoint intendedPricePoint + /// @param _transferAmount transferAmount + /// @param _commissionAmount commissionAmount + /// @return (pricePoint, tokenAmount, commissionTokenAmount) + function validateMarginAndCalculateBTAmount( + bytes3 _currency, + uint256 _intendedPricePoint, + uint256 _transferAmount, + uint256 _commissionAmount) + internal + returns (uint256, uint256, uint256) /* pricePoint, tokenAmount, commissionTokenAmount */ + { + uint256 pricePoint = getPricePoint(_currency); + require(pricePoint > 0); + require(isPricePointInRange(_intendedPricePoint, pricePoint, acceptedMargins(_currency))); + + uint256 tokenAmount; + uint256 commissionTokenAmount; + (tokenAmount, commissionTokenAmount) = getBTAmountFromCurrencyValue(pricePoint, _transferAmount, + _commissionAmount); + + return (pricePoint, tokenAmount, commissionTokenAmount); + } + } diff --git a/contracts/PricerInterface.sol b/contracts/PricerInterface.sol index 6fed0f4..11d5028 100644 --- a/contracts/PricerInterface.sol +++ b/contracts/PricerInterface.sol @@ -1,3 +1,4 @@ +/* solhint-disable-next-line compiler-fixed */ pragma solidity ^0.4.17; // Copyright 2018 OpenST Ltd. @@ -29,27 +30,31 @@ contract PricerInterface { ///Event for payment complete event Payment( address _beneficiary, - uint256 _transferAmount, + uint256 _tokenAmount, address _commissionBeneficiary, - uint256 _commissionAmount, + uint256 _commissionTokenAmount, bytes3 _currency, uint256 _intendedPricePoint, uint256 _actualPricePoint); ///Event for price oracles updates for currency event PriceOracleSet( - bytes3 _currency, - address _address); + bytes3 indexed _currency, + address indexed _address); ///Event for price oracles delete event PriceOracleUnset( - bytes3 _currency); + bytes3 indexed _currency); ///Event for accepted margin update for currency event AcceptedMarginSet( - bytes3 _currency, + bytes3 indexed _currency, uint256 _acceptedMargin); + ///Event for Removing Contract + event Removed( + address indexed _sender); + /// @dev Returns address of the branded token; /// public method; /// @return address @@ -149,6 +154,7 @@ contract PricerInterface { uint256 _commissionAmount, bytes3 _currency) public + view returns (uint256, uint256, uint256); /// @dev Takes _beneficiary, _transferAmount, _commissionBeneficiary, _commissionAmount, @@ -167,7 +173,7 @@ contract PricerInterface { /// @param _commissionAmount commissionAmount /// @param _currency currency /// @param _intendedPricePoint _intendedPricePoint - /// @return bool isSuccess + /// @return uint256 totalPaid function pay( address _beneficiary, uint256 _transferAmount, @@ -176,7 +182,7 @@ contract PricerInterface { bytes3 _currency, uint256 _intendedPricePoint) public - returns (bool); + returns (uint256); /// @dev Takes _currency; /// gets current price point for the price oracle for the given currency; @@ -185,8 +191,60 @@ contract PricerInterface { /// @return (pricePoint) function getPricePoint( bytes3 _currency) - public + public + view returns (uint256); - + + /// @dev Takes _intendedPricePoint, _currentPricePoint, _acceptedMargin; + /// checks if the current price point is in the acceptable range of intendedPricePoint; + /// internal method; + /// @param _beneficiary beneficiary + /// @param _transferAmount transferAmount + /// @param _commissionBeneficiary commissionBeneficiary + /// @param _commissionAmount commissionAmount + /// @return bool isValid + function isValidBeneficiaryData( + address _beneficiary, + uint256 _transferAmount, + address _commissionBeneficiary, + uint256 _commissionAmount) + internal + returns (bool); + + /// @dev Takes _spender, _beneficiary, _tokenAmount, _commissionBeneficiary, _commissionTokenAmount; + /// Perform tokenAmount transfer + /// Perform commissionTokenAmount transfer + /// internal method; + /// @param _spender spender + /// @param _beneficiary beneficiary + /// @param _tokenAmount tokenAmount + /// @param _commissionBeneficiary commissionBeneficiary + /// @param _commissionTokenAmount commissionTokenAmount + /// @return (bool) + function performTransfers( + address _spender, + address _beneficiary, + uint256 _tokenAmount, + address _commissionBeneficiary, + uint256 _commissionTokenAmount) + internal + returns (bool); + + /// @dev Takes _currency, _intendedPricePoint, _transferAmount, _commissionAmount; + /// Validate accepted margin + /// Calculates tokenAmount and commissionTokenAmount + /// internal method + /// @param _currency currency + /// @param _intendedPricePoint intendedPricePoint + /// @param _transferAmount transferAmount + /// @param _commissionAmount commissionAmount + /// @return (pricePoint, tokenAmount, commissionTokenAmount) + function validateMarginAndCalculateBTAmount( + bytes3 _currency, + uint256 _intendedPricePoint, + uint256 _transferAmount, + uint256 _commissionAmount) + internal + returns (uint256, uint256, uint256); } \ No newline at end of file diff --git a/contracts/Workers.sol b/contracts/Workers.sol index b0f8bc5..278582c 100644 --- a/contracts/Workers.sol +++ b/contracts/Workers.sol @@ -40,13 +40,13 @@ contract Workers is OpsManaged { */ ///Event for worker set event WorkerSet( - address _worker, - uint256 _deactivationHeight, + address indexed _worker, + uint256 indexed _deactivationHeight, uint256 _remainingHeight); ///Event for worker removed event WorkerRemoved( - address _worker, + address indexed _worker, bool _existed); /// @dev Constructor; diff --git a/contracts/ost-price-oracle/PriceOracle.sol b/contracts/ost-price-oracle/PriceOracle.sol index 80f32ea..4731ed9 100644 --- a/contracts/ost-price-oracle/PriceOracle.sol +++ b/contracts/ost-price-oracle/PriceOracle.sol @@ -103,20 +103,12 @@ contract PriceOracle is OpsManaged, PriceOracleInterface { /// @return price (Return 0 in case price expired so that call of this method can handle the error case) function getPrice() public + view returns ( uint256 /* price */ ) { // Current Block Number should be less than expiration height - // Emit an event if Price has expired - if (block.number > oracleExpirationHeight) { - // Emit invalid price event - PriceExpired(oracleExpirationHeight); - - return (0); - } - - // Return current price - return (price); + return (block.number > oracleExpirationHeight) ? 0 : price; } /// @dev use this function to get token decimals value diff --git a/contracts/ost-price-oracle/PriceOracleInterface.sol b/contracts/ost-price-oracle/PriceOracleInterface.sol index 663ca3e..439fc4c 100644 --- a/contracts/ost-price-oracle/PriceOracleInterface.sol +++ b/contracts/ost-price-oracle/PriceOracleInterface.sol @@ -1,3 +1,4 @@ +/* solhint-disable-next-line compiler-fixed */ pragma solidity ^0.4.17; // Copyright 2017 OST.com Ltd. @@ -30,12 +31,8 @@ contract PriceOracleInterface { /// @dev event emitted whenever price is updated /// @return _price /// @return _expirationHeight - event PriceUpdated(uint256 _price, - uint256 _expirationHeight); - - /// @dev event emitted if price has expired - /// @return _expirationHeight - event PriceExpired(uint256 _expirationHeight); + event PriceUpdated(uint256 indexed _price, + uint256 indexed _expirationHeight); /* * Functions @@ -94,6 +91,7 @@ contract PriceOracleInterface { /// @return quoteCurrency/baseCurrency value function getPrice() public + view returns( uint256); diff --git a/package.json b/package.json index fdde599..5bbb226 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "", "dependencies": { "@openstfoundation/openst-notification": "1.0.0-beta.1", + "@ostdotcom/ost-price-oracle": "^1.0.0-beta.3", "bignumber": "^1.1.0", "bignumber.js": "^4.1.0", "moment": "^2.19.2",