Skip to content

Commit

Permalink
Add acknowledgement for irrevocable actions using EIP712 (#599)
Browse files Browse the repository at this point in the history
* Require signed user ack

* Added test

* Small fix

* WIP

* Cleanup

* More cleanup

* Cleaned up tests

* Skip acknowledgement tests in coverage

* Test fix

* Merge fix
  • Loading branch information
maxsam4 authored and adamdossa committed Mar 22, 2019
1 parent 735ab29 commit 34bb63e
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 89 deletions.
92 changes: 92 additions & 0 deletions contracts/libraries/TokenLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ library TokenLib {

using SafeMath for uint256;

struct EIP712Domain {
string name;
uint256 chainId;
address verifyingContract;
}

struct Acknowledgment {
string text;
}

bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256(
"EIP712Domain(string name,uint256 chainId,address verifyingContract)"
);

bytes32 constant ACK_TYPEHASH = keccak256(
"Acknowledgment(string text)"
);

bytes32 internal constant WHITELIST = "WHITELIST";
bytes32 internal constant INVESTORSKEY = 0xdf3a8dd24acdd05addfc6aeffef7574d2de3f844535ec91e8e0f3e45dba96731; //keccak256(abi.encodePacked("INVESTORS"))

Expand All @@ -26,6 +44,80 @@ library TokenLib {
// Emit when the budget allocated to a module is changed
event ModuleBudgetChanged(uint8[] _moduleTypes, address _module, uint256 _oldBudget, uint256 _budget);

function hash(EIP712Domain memory _eip712Domain) internal pure returns (bytes32) {
return keccak256(
abi.encode(
EIP712DOMAIN_TYPEHASH,
keccak256(bytes(_eip712Domain.name)),
_eip712Domain.chainId,
_eip712Domain.verifyingContract
)
);
}

function hash(Acknowledgment memory _ack) internal pure returns (bytes32) {
return keccak256(abi.encode(ACK_TYPEHASH, keccak256(bytes(_ack.text))));
}

function recoverFreezeIssuanceAckSigner(bytes memory _signature) public view returns (address) {
Acknowledgment memory ack = Acknowledgment("I acknowledge that freezing Issuance is a permanent and irrevocable change");
return extractSigner(ack, _signature);
}

function recoverDisableControllerAckSigner(bytes memory _signature) public view returns (address) {
Acknowledgment memory ack = Acknowledgment("I acknowledge that disabling controller is a permanent and irrevocable change");
return extractSigner(ack, _signature);
}

function extractSigner(Acknowledgment memory _ack, bytes memory _signature) internal view returns (address) {
bytes32 r;
bytes32 s;
uint8 v;

// Check the signature length
if (_signature.length != 65) {
return (address(0));
}

// Divide the signature in r, s and v variables
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
// solhint-disable-next-line no-inline-assembly
assembly {
r := mload(add(_signature, 0x20))
s := mload(add(_signature, 0x40))
v := byte(0, mload(add(_signature, 0x60)))
}

// Version of signature should be 27 or 28, but 0 and 1 are also possible versions
if (v < 27) {
v += 27;
}

// If the version is correct return the signer address
if (v != 27 && v != 28) {
return (address(0));
}

bytes32 DOMAIN_SEPARATOR = hash(
EIP712Domain(
{
name: "Polymath",
chainId: 1,
verifyingContract: address(this)
}
)
);

// Note: we need to use `encodePacked` here instead of `encode`.
bytes32 digest = keccak256(abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
hash(_ack)
));
return ecrecover(digest, v, r, s);
}

/**
* @notice Archives a module attached to the SecurityToken
* @param _moduleData Storage data
Expand Down
13 changes: 4 additions & 9 deletions contracts/tokens/SecurityToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import "../interfaces/token/IERC1594.sol";
import "../interfaces/token/IERC1643.sol";
import "../interfaces/token/IERC1644.sol";
import "../interfaces/IModuleRegistry.sol";
import "../interfaces/IFeatureRegistry.sol";
import "../interfaces/ITransferManager.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol";
Expand Down Expand Up @@ -115,11 +114,6 @@ contract SecurityToken is ERC20, ERC20Detailed, Ownable, ReentrancyGuard, Securi
_;
}

modifier isEnabled(string memory _nameKey) {
require(IFeatureRegistry(featureRegistry).getFeatureStatus(_nameKey));
_;
}

/**
* @notice constructor
* @param _name Name of the SecurityToken
Expand Down Expand Up @@ -485,7 +479,8 @@ contract SecurityToken is ERC20, ERC20Detailed, Ownable, ReentrancyGuard, Securi
* @notice Permanently freeze issuance of this security token.
* @dev It MUST NOT be possible to increase `totalSuppy` after this function is called.
*/
function freezeIssuance() external isIssuanceAllowed isEnabled("freezeIssuanceAllowed") onlyOwner {
function freezeIssuance(bytes calldata _signature) external isIssuanceAllowed onlyOwner {
require(owner() == TokenLib.recoverFreezeIssuanceAckSigner(_signature), "Owner did not sign");
issuance = false;
/*solium-disable-next-line security/no-block-members*/
emit FreezeIssuance();
Expand Down Expand Up @@ -614,7 +609,8 @@ contract SecurityToken is ERC20, ERC20Detailed, Ownable, ReentrancyGuard, Securi
* @notice Used by the issuer to permanently disable controller functionality
* @dev enabled via feature switch "disableControllerAllowed"
*/
function disableController() external isEnabled("disableControllerAllowed") onlyOwner {
function disableController(bytes calldata _signature) external onlyOwner {
require(owner() == TokenLib.recoverDisableControllerAckSigner(_signature), "Owner did not sign");
require(isControllable());
controllerDisabled = true;
delete controller;
Expand Down Expand Up @@ -768,7 +764,6 @@ contract SecurityToken is ERC20, ERC20Detailed, Ownable, ReentrancyGuard, Securi
function updateFromRegistry() public onlyOwner {
moduleRegistry = PolymathRegistry(polymathRegistry).getAddress("ModuleRegistry");
securityTokenRegistry = PolymathRegistry(polymathRegistry).getAddress("SecurityTokenRegistry");
featureRegistry = PolymathRegistry(polymathRegistry).getAddress("FeatureRegistry");
polyToken = PolymathRegistry(polymathRegistry).getAddress("PolyToken");
}
}
1 change: 0 additions & 1 deletion contracts/tokens/SecurityTokenStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ contract SecurityTokenStorage {
address public polymathRegistry;
address public moduleRegistry;
address public securityTokenRegistry;
address public featureRegistry;
address public polyToken;
address public delegate;
// Address of the data store used to store shared data
Expand Down
89 changes: 88 additions & 1 deletion test/helpers/signData.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const Web3 = require("web3");
//const sigUtil = require('eth-sig-util')
let BN = Web3.utils.BN;

function getSignSTMData(tmAddress, nonce, validFrom, expiry, fromAddress, toAddress, amount, pk) {
Expand Down Expand Up @@ -32,6 +33,90 @@ function getSignSTMData(tmAddress, nonce, validFrom, expiry, fromAddress, toAddr
return data;
}

async function getFreezeIssuanceAck(stAddress, from) {
const typedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' }
],
Acknowledgment: [
{ name: 'text', type: 'string' }
],
},
primaryType: 'Acknowledgment',
domain: {
name: 'Polymath',
chainId: 1,
verifyingContract: stAddress
},
message: {
text: 'I acknowledge that freezing Issuance is a permanent and irrevocable change',
},
};
const result = await new Promise((resolve, reject) => {
web3.currentProvider.send(
{
method: 'eth_signTypedData',
params: [from, typedData]
},
(err, result) => {
if (err) {
return reject(err);
}
resolve(result.result);
}
);
});
// console.log('signed by', from);
// const recovered = sigUtil.recoverTypedSignature({
// data: typedData,
// sig: result
// })
// console.log('recovered address', recovered);
return result;
}

async function getDisableControllerAck(stAddress, from) {
const typedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' }
],
Acknowledgment: [
{ name: 'text', type: 'string' }
],
},
primaryType: 'Acknowledgment',
domain: {
name: 'Polymath',
chainId: 1,
verifyingContract: stAddress
},
message: {
text: 'I acknowledge that disabling controller is a permanent and irrevocable change',
},
};
const result = await new Promise((resolve, reject) => {
web3.currentProvider.send(
{
method: 'eth_signTypedData',
params: [from, typedData]
},
(err, result) => {
if (err) {
return reject(err);
}
resolve(result.result);
}
);
});
return result;
}

function getSignGTMData(tmAddress, investorAddress, fromTime, toTime, expiryTime, validFrom, validTo, nonce, pk) {
let hash = web3.utils.soliditySha3({
t: 'address',
Expand Down Expand Up @@ -109,5 +194,7 @@ module.exports = {
getSignSTMData,
getSignGTMData,
getSignGTMTransferData,
getMultiSignGTMData
getMultiSignGTMData,
getFreezeIssuanceAck,
getDisableControllerAck
};
Loading

0 comments on commit 34bb63e

Please sign in to comment.