diff --git a/contracts/Hasher.sol b/contracts/Hasher.sol index 4134d428..3b575605 100644 --- a/contracts/Hasher.sol +++ b/contracts/Hasher.sol @@ -46,10 +46,11 @@ contract Hasher { _conversionRate); } - function hashMintingIntent( + function hashStakingIntent( bytes32 _uuid, address _account, uint256 _accountNonce, + address _beneficiary, uint256 _amountST, uint256 _amountUT, uint256 _escrowUnlockHeight) @@ -61,6 +62,7 @@ contract Hasher { _uuid, _account, _accountNonce, + _beneficiary, _amountST, _amountUT, _escrowUnlockHeight); diff --git a/contracts/Registrar.sol b/contracts/Registrar.sol index dbd2f38d..2948ba47 100644 --- a/contracts/Registrar.sol +++ b/contracts/Registrar.sol @@ -36,8 +36,5 @@ contract Registrar is OpsManaged { { } - - - } \ No newline at end of file diff --git a/contracts/utilityChain/OpenSTUtility.sol b/contracts/utilityChain/OpenSTUtility.sol index d7d967d0..fa96fa97 100644 --- a/contracts/utilityChain/OpenSTUtility.sol +++ b/contracts/utilityChain/OpenSTUtility.sol @@ -37,10 +37,25 @@ contract OpenSTUtility is Hasher, OpsManaged { * Structures */ struct RegisteredToken { - address tokenAddress; + UtilityTokenInterface token; address registrar; } + + struct Mint { + bytes32 uuid; + address staker; + address beneficiary; + uint256 amount; + uint256 unlockHeight; + } + + struct Redemption { + bytes32 uuid; + address redeemer; + uint256 amountUT; + uint256 escrowUnlockHeight; + } /* * Events */ @@ -48,10 +63,15 @@ contract OpenSTUtility is Hasher, OpsManaged { bytes32 _uuid, string _symbol, string _name, uint256 _conversionRate); event RegisteredBrandedToken(address indexed _registrar, address indexed _token, bytes32 _uuid, string _symbol, string _name, uint256 _conversionRate, address _requester); + event StakingIntentConfirmed(bytes32 indexed _uuid, bytes32 indexed _stakingIntentHash, + address _staker, address _beneficiary, uint256 _amountST, uint256 _amountUT, uint256 unlockHeight); + event ProcessedMint(bytes32 indexed _uuid, bytes32 indexed _stakingIntentHash, address _staker, + address _beneficiary, uint256 _amount); + /* * Constants */ - string public constant STPRIME_SYMBOL = "STP"; + string public constant STPRIME_SYMBOL = "STP"; string public constant STPRIME_NAME = "SimpleTokenPrime"; uint8 public constant TOKEN_DECIMALS = 18; uint256 public constant DECIMALSFACTOR = 10**uint256(TOKEN_DECIMALS); @@ -63,7 +83,6 @@ contract OpenSTUtility is Hasher, OpsManaged { /* * Storage */ - // mapping() /// store address of Simple Token Prime address public simpleTokenPrime; bytes32 public uuidSTPrime; @@ -79,7 +98,16 @@ contract OpenSTUtility is Hasher, OpsManaged { /// symbol reserved for unique API routes /// and resolves to address mapping(bytes32 /* hashSymbol */ => address /* UtilityToken */) public symbolRoute; - + /// nonce makes the staking process atomic across the two-phased process + /// and protects against replay attack on (un)staking proofs during the process. + /// On the value chain nonces need to strictly increase by one; on the utility + /// chain the nonce need to strictly increase (as one value chain can have multiple + /// utility chains) + mapping(address /* (un)staker */ => uint256) nonces; + /// store the ongoing mints and redemptions + mapping(bytes32 /* stakingIntentHash */ => Mint) mints; + mapping(bytes32 /* redemptionIntentHash*/ => Redemption) redemptions; + /* * Modifiers */ @@ -119,8 +147,8 @@ contract OpenSTUtility is Hasher, OpsManaged { uuidSTPrime); registeredTokens[uuidSTPrime] = RegisteredToken({ - tokenAddress: simpleTokenPrime, - registrar: registrar + token: UtilityTokenInterface(simpleTokenPrime), + registrar: registrar }); // lock name and symbol route for ST' @@ -252,11 +280,11 @@ contract OpenSTUtility is Hasher, OpsManaged { require(uuid == _checkUuid); require(_brandedToken.uuid() == _checkUuid); - assert(registeredTokens[uuid].tokenAddress == address(0)); + assert(address(registeredTokens[uuid].token) == address(0)); registeredTokens[uuid] = RegisteredToken({ - tokenAddress: _brandedToken, - registrar: registrar + token: _brandedToken, + registrar: registrar }); // register name to registrar @@ -270,11 +298,86 @@ contract OpenSTUtility is Hasher, OpsManaged { return uuid; } + function confirmStakingIntent( + bytes32 _uuid, + address _staker, + uint256 _stakerNonce, + address _beneficiary, + uint256 _amountST, + uint256 _amountUT, + uint256 _stakingUnlockHeight, + bytes32 _stakingIntentHash) + external + onlyRegistrar + returns (uint256 unlockHeight) + { + require(address(registeredTokens[_uuid].token) != address(0)); + + require(nonces[_staker] < _stakerNonce); + require(_amountST > 0); + require(_amountUT > 0); + // escrowunlockheight needs to be checked against the core that tracks the value chain + require(_stakingUnlockHeight > 0); + require(_stakingIntentHash != ""); + + unlockHeight = block.number + BLOCKS_TO_WAIT_SHORT; + nonces[_staker] = _stakerNonce; + + bytes32 stakingIntentHash = hashStakingIntent( + _uuid, + _staker, + _stakerNonce, + _beneficiary, + _amountST, + _amountUT, + _stakingUnlockHeight + ); + + require(stakingIntentHash == _stakingIntentHash); + + mints[stakingIntentHash] = Mint({ + uuid: _uuid, + staker: _staker, + beneficiary: _beneficiary, + amount: _amountUT, + unlockHeight: unlockHeight + }); + + StakingIntentConfirmed(_uuid, _stakingIntentHash, _staker, _beneficiary, _amountST, + _amountUT, unlockHeight); + + return unlockHeight; + } + + function processMinting( + bytes32 _stakingIntentHash) + external + returns (address tokenAddress) + { + require(_stakingIntentHash != ""); + + Mint storage mint = mints[_stakingIntentHash]; + require(mint.staker == msg.sender); + require(mint.unlockHeight > block.number); + + UtilityTokenInterface token = registeredTokens[mint.uuid].token; + tokenAddress = address(token); + require(tokenAddress != address(0)); + + require(token.mint(mint.beneficiary, mint.amount)); + + ProcessedMint(mint.uuid, _stakingIntentHash, mint.staker, mint.beneficiary, mint.amount); + + delete mints[_stakingIntentHash]; + + return tokenAddress; + } + + /* * Operation functions */ /// @dev TODO: add events to trigger for each action - function addNameReservation( bytes32 _hashName, address _requester) diff --git a/contracts/utilityChain/UtilityTokenAbstract.sol b/contracts/utilityChain/UtilityTokenAbstract.sol index 7c026e17..ad089e05 100644 --- a/contracts/utilityChain/UtilityTokenAbstract.sol +++ b/contracts/utilityChain/UtilityTokenAbstract.sol @@ -23,9 +23,10 @@ pragma solidity ^0.4.17; import "../SafeMath.sol"; import "../ProtocolVersioned.sol"; +import "./UtilityTokenInterface.sol"; /// @title UtilityToken abstract -contract UtilityTokenAbstract is ProtocolVersioned { +contract UtilityTokenAbstract is ProtocolVersioned, UtilityTokenInterface { using SafeMath for uint256; /* @@ -61,13 +62,13 @@ contract UtilityTokenAbstract is ProtocolVersioned { totalTokenSupply = 0; } - /// @dev transfer full claim to beneficiary - function claim(address _beneficiary) public returns (bool success); - /// @dev Mint new utility token into - function mint(address _beneficiary, uint256 _amount) public returns (bool success); - /// @dev Burn utility tokens after having redeemed them - /// through the protocol for the staked Simple Token - function burn(address _burner, uint256 _amount) public payable returns (bool success); + // /// @dev transfer full claim to beneficiary + // function claim(address _beneficiary) public returns (bool success); + // /// @dev Mint new utility token into + // function mint(address _beneficiary, uint256 _amount) public returns (bool success); + // /// @dev Burn utility tokens after having redeemed them + // /// through the protocol for the staked Simple Token + // function burn(address _burner, uint256 _amount) public payable returns (bool success); /// @dev Get totalTokenSupply as view so that child cannot edit function totalSupply() diff --git a/contracts/utilityChain/UtilityTokenInterface.sol b/contracts/utilityChain/UtilityTokenInterface.sol new file mode 100644 index 00000000..7bc2ea87 --- /dev/null +++ b/contracts/utilityChain/UtilityTokenInterface.sol @@ -0,0 +1,36 @@ +pragma solidity ^0.4.17; + +// Copyright 2017 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. +// +// ---------------------------------------------------------------------------- +// contracts/utilityChain/UtilityTokenInterface.sol +// +// http://www.simpletoken.org/ +// +// ---------------------------------------------------------------------------- + +contract UtilityTokenInterface { + + /// @dev transfer full claim to beneficiary + function claim(address _beneficiary) public returns (bool success); + /// @dev Mint new utility token into claim for beneficiary + function mint(address _beneficiary, uint256 _amount) public returns (bool success); + /// @dev Burn utility tokens after having redeemed them + /// through the protocol for the staked Simple Token + function burn(address _burner, uint256 _amount) public payable returns (bool success); + + /// @dev Get totalTokenSupply as view so that child cannot edit + function totalSupply() public view returns (uint256 supply); +} \ No newline at end of file diff --git a/contracts/valueChain/OpenSTValue.sol b/contracts/valueChain/OpenSTValue.sol index 0dc1c22b..b840029d 100644 --- a/contracts/valueChain/OpenSTValue.sol +++ b/contracts/valueChain/OpenSTValue.sol @@ -39,6 +39,9 @@ contract OpenSTValue is OpsManaged, Hasher { event UtilityTokenRegistered(bytes32 indexed _uuid, address indexed stake, string _symbol, string _name, uint8 _decimals, uint256 _conversionRate, uint256 _chainIdUtility, address indexed _stakingAccount); + event StakingIntentDeclared(bytes32 indexed _uuid, address indexed _staker, + uint256 _stakerNonce, address _beneficiary, uint256 _amountST, + uint256 _amountUT, uint256 _escrowUnlockHeight, bytes32 _stakingIntentHash); /* * Constants @@ -61,19 +64,20 @@ contract OpenSTValue is OpsManaged, Hasher { uint256 chainIdUtility; address simpleStake; address stakingAccount; - mapping(bytes32 /* hashStakingIntent */ => Stake) stakes; - mapping(bytes32 /* hashRedemptionIntent */ => Unstake) unstakes; } struct Stake { + bytes32 uuid; address staker; address beneficiary; + uint256 nonce; uint256 amountST; uint256 amountUT; uint256 escrowUnlockHeight; } struct Unstake { + bytes32 uuid; address unstaker; uint256 amount; } @@ -86,6 +90,16 @@ contract OpenSTValue is OpsManaged, Hasher { address public registrar; mapping(uint256 /* chainIdUtility */ => CoreInterface) cores; mapping(bytes32 /* uuid */ => UtilityToken) utilityTokens; + /// nonce makes the staking process atomic across the two-phased process + /// and protects against replay attack on (un)staking proofs during the process. + /// On the value chain nonces need to strictly increase by one; on the utility + /// chain the nonce need to strictly increase (as one value chain can have multiple + /// utility chains) + mapping(address /* (un)staker */ => uint256) nonces; + /// register the active stakes and unstakes + mapping(bytes32 /* hashStakingIntent */ => Stake) stakes; + mapping(bytes32 /* hashRedemptionIntent */ => Unstake) unstakes; + /* * Modifiers @@ -120,8 +134,69 @@ contract OpenSTValue is OpsManaged, Hasher { /// @dev In order to stake the tx.origin needs to set an allowance /// for the OpenSTValue contract to transfer to itself to hold /// during the staking process. - // function stake( - // ) + function stake( + bytes32 _uuid, + uint256 _amountST, + address _beneficiary) + external + returns ( + uint256 amountUT, + uint256 nonce, + uint256 unlockHeight, + bytes32 stakingIntentHash) + { + // check the staking contract has been approved to spend the amount to stake + // OpenSTValue needs to be able to transfer the stake into its balance for + // keeping until the two-phase process is completed on both chains. + require(_amountST > 0); + // Consider the security risk of using tx.origin; at the same time an allowance + // needs to be set before calling stake over a potentially malicious contract at stakingAccount. + // The second protection is that the staker needs to check the intent hash before + // signing off on completing the two-phased process. + require(valueToken.allowance(tx.origin, address(this)) >= _amountST); + + require(utilityTokens[_uuid].simpleStake != address(0)); + require(_beneficiary != address(0)); + + UtilityToken storage utilityToken = utilityTokens[_uuid]; + + // if the staking account is set to a non-zero address, + // then all transactions have come (from/over) the staking account, + // whether this is an EOA or a contract; tx.origin is putting forward the funds + if (utilityToken.stakingAccount != address(0)) require(msg.sender == utilityToken.stakingAccount); + require(valueToken.transferFrom(tx.origin, address(this), _amountST)); + + amountUT = _amountST.mul(utilityToken.conversionRate); + unlockHeight = block.number + BLOCKS_TO_WAIT_LONG; + + nonces[tx.origin]++; + nonce = nonces[tx.origin]; + + stakingIntentHash = hashStakingIntent( + _uuid, + tx.origin, + nonce, + _beneficiary, + _amountST, + amountUT, + unlockHeight + ); + + stakes[stakingIntentHash] = Stake({ + uuid: _uuid, + staker: tx.origin, + beneficiary: _beneficiary, + nonce: nonce, + amountST: _amountST, + amountUT: amountUT, + escrowUnlockHeight: unlockHeight + }); + + StakingIntentDeclared(_uuid, tx.origin, nonce, _beneficiary, + _amountST, amountUT, unlockHeight, stakingIntentHash); + + return (amountUT, nonce, unlockHeight, stakingIntentHash); + } /*