diff --git a/contracts/colony/Colony.sol b/contracts/colony/Colony.sol
index 4b512dc8b5..e1c0fbfafb 100755
--- a/contracts/colony/Colony.sol
+++ b/contracts/colony/Colony.sol
@@ -20,11 +20,11 @@ pragma experimental ABIEncoderV2;
import "./../common/ERC20Extended.sol";
import "./../common/IEtherRouter.sol";
-import "./../common/MultiChain.sol";
+import "./../common/BasicMetaTransaction.sol";
import "./../tokenLocking/ITokenLocking.sol";
import "./ColonyStorage.sol";
-contract Colony is ColonyStorage, PatriciaTreeProofs, MultiChain {
+contract Colony is BasicMetaTransaction, ColonyStorage, PatriciaTreeProofs {
// V8: Ebony Lightweight Spaceship
// This function, exactly as defined, is used in build scripts. Take care when updating.
@@ -39,144 +39,8 @@ contract Colony is ColonyStorage, PatriciaTreeProofs, MultiChain {
return token;
}
- bytes4 constant APPROVE_SIG = bytes4(keccak256("approve(address,uint256)"));
- bytes4 constant TRANSFER_SIG = bytes4(keccak256("transfer(address,uint256)"));
- bytes4 constant TRANSFER_FROM_SIG = bytes4(keccak256("transferFrom(address,address,uint256)"));
- bytes4 constant BURN_SIG = bytes4(keccak256("burn(uint256)"));
- bytes4 constant BURN_GUY_SIG = bytes4(keccak256("burn(address,uint256)"));
-
- function makeArbitraryTransaction(address _to, bytes memory _action)
- public stoppable auth
- returns (bool)
- {
- return this.makeSingleArbitraryTransaction(_to, _action);
- }
-
- function makeArbitraryTransactions(address[] memory _targets, bytes[] memory _actions, bool _strict)
- public stoppable auth
- returns (bool)
- {
- require(_targets.length == _actions.length, "colony-targets-and-actions-length-mismatch");
- for (uint256 i; i < _targets.length; i += 1){
- bool success = true;
- // slither-disable-next-line unused-return
- try this.makeSingleArbitraryTransaction(_targets[i], _actions[i]) returns (bool ret){
- if (_strict){
- success = ret;
- }
- } catch {
- // We failed in a require, which is only okay if we're not in strict mode
- if (_strict){
- success = false;
- }
- }
- require(success, "colony-arbitrary-transaction-failed");
- }
- return true;
- }
-
- function makeSingleArbitraryTransaction(address _to, bytes memory _action)
- external stoppable self
- returns (bool)
- {
- // Prevent transactions to network contracts
- require(_to != address(this), "colony-cannot-target-self");
- require(_to != colonyNetworkAddress, "colony-cannot-target-network");
- require(_to != tokenLockingAddress, "colony-cannot-target-token-locking");
-
- // Prevent transactions to transfer held tokens
- bytes4 sig;
- assembly { sig := mload(add(_action, 0x20)) }
-
- if (sig == APPROVE_SIG) { approveTransactionPreparation(_to, _action); }
- else if (sig == BURN_SIG) { burnTransactionPreparation(_to, _action); }
- else if (sig == TRANSFER_SIG) { transferTransactionPreparation(_to, _action); }
- else if (sig == BURN_GUY_SIG || sig == TRANSFER_FROM_SIG) { burnGuyOrTransferFromTransactionPreparation(_action); }
-
- // Prevent transactions to network-managed extensions installed in this colony
- require(isContract(_to), "colony-to-must-be-contract");
- // slither-disable-next-line unused-return
- try ColonyExtension(_to).identifier() returns (bytes32 extensionId) {
- require(
- IColonyNetwork(colonyNetworkAddress).getExtensionInstallation(extensionId, address(this)) != _to,
- "colony-cannot-target-extensions"
- );
- } catch {}
-
- bool res = executeCall(_to, 0, _action);
-
- if (sig == APPROVE_SIG) { approveTransactionCleanup(_to, _action); }
-
- return res;
- }
-
- function approveTransactionPreparation(address _to, bytes memory _action) internal {
- address spender;
- assembly {
- spender := mload(add(_action, 0x24))
- }
- updateApprovalAmountInternal(_to, spender, false);
- }
-
- function approveTransactionCleanup(address _to, bytes memory _action) internal {
- address spender;
- assembly {
- spender := mload(add(_action, 0x24))
- }
- updateApprovalAmountInternal(_to, spender, true);
- }
-
- function burnTransactionPreparation(address _to, bytes memory _action) internal {
- uint256 amount;
- assembly {
- amount := mload(add(_action, 0x24))
- }
- fundingPots[1].balance[_to] = sub(fundingPots[1].balance[_to], amount);
- require(fundingPots[1].balance[_to] >= tokenApprovalTotals[_to], "colony-not-enough-tokens");
- }
-
- function transferTransactionPreparation(address _to, bytes memory _action) internal {
- uint256 amount;
- assembly {
- amount := mload(add(_action, 0x44))
- }
- fundingPots[1].balance[_to] = sub(fundingPots[1].balance[_to], amount);
- require(fundingPots[1].balance[_to] >= tokenApprovalTotals[_to], "colony-not-enough-tokens");
- }
-
- function burnGuyOrTransferFromTransactionPreparation(bytes memory _action) internal {
- address spender;
- assembly {
- spender := mload(add(_action, 0x24))
- }
- require(spender != address(this), "colony-cannot-spend-own-allowance");
- }
-
- function updateApprovalAmount(address _token, address _spender) stoppable public {
- updateApprovalAmountInternal(_token, _spender, false);
- }
-
- function updateApprovalAmountInternal(address _token, address _spender, bool _postApproval) internal {
- uint256 recordedApproval = tokenApprovals[_token][_spender];
- uint256 actualApproval = ERC20Extended(_token).allowance(address(this), _spender);
- if (recordedApproval == actualApproval) {
- return;
- }
-
- if (recordedApproval > actualApproval && !_postApproval){
- // They've spend some tokens out of root. Adjust balances accordingly
- // If we are post approval, then they have not spent tokens
- fundingPots[1].balance[_token] = add(sub(fundingPots[1].balance[_token], recordedApproval), actualApproval);
- }
-
- tokenApprovalTotals[_token] = add(sub(tokenApprovalTotals[_token], recordedApproval), actualApproval);
- require(fundingPots[1].balance[_token] >= tokenApprovalTotals[_token], "colony-approval-exceeds-balance");
-
- tokenApprovals[_token][_spender] = actualApproval;
- }
-
function annotateTransaction(bytes32 _txHash, string memory _metadata) public always {
- emit Annotation(msg.sender, _txHash, _metadata);
+ emit Annotation(msgSender(), _txHash, _metadata);
}
function emitDomainReputationReward(uint256 _domainId, address _user, int256 _amount)
@@ -256,19 +120,13 @@ contract Colony is ColonyStorage, PatriciaTreeProofs, MultiChain {
// Set initial colony reward inverse amount to the max indicating a zero rewards to start with
rewardInverse = 2**256 - 1;
- emit ColonyInitialised(msg.sender, _colonyNetworkAddress, _token);
- }
-
- function initialiseColony(address _colonyNetworkAddress, address _token, string memory _metadata) public stoppable {
- initialiseColony(_colonyNetworkAddress, _token);
-
- emit ColonyMetadata(msg.sender, _metadata);
+ emit ColonyInitialised(msgSender(), _colonyNetworkAddress, _token);
}
function editColony(string memory _metadata) public
stoppable
auth {
- emit ColonyMetadata(msg.sender, _metadata);
+ emit ColonyMetadata(msgSender(), _metadata);
}
function bootstrapColony(address[] memory _users, int[] memory _amounts) public
@@ -288,7 +146,17 @@ contract Colony is ColonyStorage, PatriciaTreeProofs, MultiChain {
IColonyNetwork(colonyNetworkAddress).appendReputationUpdateLog(_users[i], _amounts[i], domains[1].skillId);
}
- emit ColonyBootstrapped(msg.sender, _users, _amounts);
+ emit ColonyBootstrapped(msgSender(), _users, _amounts);
+ }
+
+ function burnTokens(address _token, uint256 _amount) public stoppable auth {
+ // Check the root funding pot has enought
+ require(fundingPots[1].balance[_token] >= _amount, "colony-not-enough-tokens");
+ fundingPots[1].balance[_token] -= _amount;
+
+ ERC20Extended(_token).burn(_amount);
+
+ emit TokensBurned(msgSender(), _token, _amount);
}
function mintTokens(uint _wad) public
@@ -297,7 +165,7 @@ contract Colony is ColonyStorage, PatriciaTreeProofs, MultiChain {
{
ERC20Extended(token).mint(address(this), _wad); // ignore-swc-107
- emit TokensMinted(msg.sender, address(this), _wad);
+ emit TokensMinted(msgSender(), address(this), _wad);
}
function mintTokensFor(address _guy, uint _wad) public
@@ -306,12 +174,12 @@ contract Colony is ColonyStorage, PatriciaTreeProofs, MultiChain {
{
ERC20Extended(token).mint(_guy, _wad); // ignore-swc-107
- emit TokensMinted(msg.sender, _guy, _wad);
+ emit TokensMinted(msgSender(), _guy, _wad);
}
function mintTokensForColonyNetwork(uint _wad) public stoppable {
// Only the colony Network can call this function
- require(msg.sender == colonyNetworkAddress, "colony-access-denied-only-network-allowed");
+ require(msgSender() == colonyNetworkAddress, "colony-access-denied-only-network-allowed");
// Function only valid on the Meta Colony
require(address(this) == IColonyNetwork(colonyNetworkAddress).getMetaColony(), "colony-access-denied-only-meta-colony-allowed");
// Not callable on Xdai
@@ -320,7 +188,7 @@ contract Colony is ColonyStorage, PatriciaTreeProofs, MultiChain {
ERC20Extended(token).mint(_wad);
assert(ERC20Extended(token).transfer(colonyNetworkAddress, _wad));
- emit TokensMinted(msg.sender, colonyNetworkAddress, _wad);
+ emit TokensMinted(msgSender(), colonyNetworkAddress, _wad);
}
function registerColonyLabel(string memory colonyName, string memory orbitdb) public stoppable auth {
@@ -429,7 +297,7 @@ contract Colony is ColonyStorage, PatriciaTreeProofs, MultiChain {
initialiseDomain(newLocalSkill);
if (keccak256(abi.encodePacked(_metadata)) != keccak256(abi.encodePacked(""))) {
- emit DomainMetadata(msg.sender, domainCount, _metadata);
+ emit DomainMetadata(msgSender(), domainCount, _metadata);
}
}
@@ -438,7 +306,7 @@ contract Colony is ColonyStorage, PatriciaTreeProofs, MultiChain {
authDomain(_permissionDomainId, _childSkillIndex, _domainId)
{
if (keccak256(abi.encodePacked(_metadata)) != keccak256(abi.encodePacked(""))) {
- emit DomainMetadata(msg.sender, _domainId, _metadata);
+ emit DomainMetadata(msgSender(), _domainId, _metadata);
}
}
@@ -467,7 +335,7 @@ contract Colony is ColonyStorage, PatriciaTreeProofs, MultiChain {
userAddress >>= 96;
// Require that the user is proving their own reputation in this colony.
- if (address(colonyAddress) != address(this) || address(userAddress) != msg.sender) {
+ if (address(colonyAddress) != address(this) || address(userAddress) != msgSender()) {
return false;
}
@@ -495,7 +363,7 @@ contract Colony is ColonyStorage, PatriciaTreeProofs, MultiChain {
// we need to do once we know what's in it!
this.finishUpgrade();
- emit ColonyUpgraded(msg.sender, currentVersion, _newVersion);
+ emit ColonyUpgraded(msgSender(), currentVersion, _newVersion);
}
// v7 to v8
@@ -513,25 +381,39 @@ contract Colony is ColonyStorage, PatriciaTreeProofs, MultiChain {
colonyAuthority.setRoleCapability(uint8(ColonyRole.Arbitration), address(this), sig, true);
}
+ function getMetatransactionNonce(address _user) override public view returns (uint256 nonce){
+ return metatransactionNonces[_user];
+ }
+
+ function incrementMetatransactionNonce(address _user) override internal {
+ // We need to protect the metatransaction nonce slots, otherwise those with recovery
+ // permissions could replay metatransactions, which would be a disaster.
+ // What slot are we setting?
+ // This mapping is in slot 34 (see ColonyStorage.sol);
+ uint256 slot = uint256(keccak256(abi.encode(uint256(_user), uint256(METATRANSACTION_NONCES_SLOT))));
+ protectSlot(slot);
+ metatransactionNonces[_user] = add(metatransactionNonces[_user], 1);
+ }
+
function checkNotAdditionalProtectedVariable(uint256 _slot) public view recovery {
require(_slot != COLONY_NETWORK_SLOT, "colony-protected-variable");
}
function approveStake(address _approvee, uint256 _domainId, uint256 _amount) public stoppable {
- approvals[msg.sender][_approvee][_domainId] = add(approvals[msg.sender][_approvee][_domainId], _amount);
+ approvals[msgSender()][_approvee][_domainId] = add(approvals[msgSender()][_approvee][_domainId], _amount);
- ITokenLocking(tokenLockingAddress).approveStake(msg.sender, _amount, token);
+ ITokenLocking(tokenLockingAddress).approveStake(msgSender(), _amount, token);
}
function obligateStake(address _user, uint256 _domainId, uint256 _amount) public stoppable {
- approvals[_user][msg.sender][_domainId] = sub(approvals[_user][msg.sender][_domainId], _amount);
- obligations[_user][msg.sender][_domainId] = add(obligations[_user][msg.sender][_domainId], _amount);
+ approvals[_user][msgSender()][_domainId] = sub(approvals[_user][msgSender()][_domainId], _amount);
+ obligations[_user][msgSender()][_domainId] = add(obligations[_user][msgSender()][_domainId], _amount);
ITokenLocking(tokenLockingAddress).obligateStake(_user, _amount, token);
}
function deobligateStake(address _user, uint256 _domainId, uint256 _amount) public stoppable {
- obligations[_user][msg.sender][_domainId] = sub(obligations[_user][msg.sender][_domainId], _amount);
+ obligations[_user][msgSender()][_domainId] = sub(obligations[_user][msgSender()][_domainId], _amount);
ITokenLocking(tokenLockingAddress).deobligateStake(_user, _amount, token);
}
@@ -586,7 +468,7 @@ contract Colony is ColonyStorage, PatriciaTreeProofs, MultiChain {
fundingPotId: fundingPotCount
});
- emit DomainAdded(msg.sender, domainCount);
+ emit DomainAdded(msgSender(), domainCount);
emit FundingPotAdded(fundingPotCount);
}
diff --git a/contracts/colony/ColonyArbitraryTransaction.sol b/contracts/colony/ColonyArbitraryTransaction.sol
new file mode 100644
index 0000000000..a77db43895
--- /dev/null
+++ b/contracts/colony/ColonyArbitraryTransaction.sol
@@ -0,0 +1,165 @@
+/*
+ This file is part of The Colony Network.
+
+ The Colony Network is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ The Colony Network is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with The Colony Network. If not, see .
+*/
+
+pragma solidity 0.7.3;
+pragma experimental ABIEncoderV2;
+
+import "./../common/ERC20Extended.sol";
+import "./../common/IEtherRouter.sol";
+import "./../common/MultiChain.sol";
+import "./../tokenLocking/ITokenLocking.sol";
+import "./ColonyStorage.sol";
+
+contract ColonyArbitraryTransaction is ColonyStorage {
+
+ bytes4 constant APPROVE_SIG = bytes4(keccak256("approve(address,uint256)"));
+ bytes4 constant TRANSFER_SIG = bytes4(keccak256("transfer(address,uint256)"));
+ bytes4 constant TRANSFER_FROM_SIG = bytes4(keccak256("transferFrom(address,address,uint256)"));
+ bytes4 constant BURN_SIG = bytes4(keccak256("burn(uint256)"));
+ bytes4 constant BURN_GUY_SIG = bytes4(keccak256("burn(address,uint256)"));
+
+ function makeArbitraryTransaction(address _to, bytes memory _action)
+ public stoppable auth
+ returns (bool)
+ {
+ return this.makeSingleArbitraryTransaction(_to, _action);
+ }
+
+ function makeArbitraryTransactions(address[] memory _targets, bytes[] memory _actions, bool _strict)
+ public stoppable auth
+ returns (bool)
+ {
+ require(_targets.length == _actions.length, "colony-targets-and-actions-length-mismatch");
+ for (uint256 i; i < _targets.length; i += 1){
+ bool success = true;
+ // slither-disable-next-line unused-return
+ try this.makeSingleArbitraryTransaction(_targets[i], _actions[i]) returns (bool ret){
+ if (_strict){
+ success = ret;
+ }
+ } catch {
+ // We failed in a require, which is only okay if we're not in strict mode
+ if (_strict){
+ success = false;
+ }
+ }
+ require(success, "colony-arbitrary-transaction-failed");
+ }
+ return true;
+ }
+
+ function makeSingleArbitraryTransaction(address _to, bytes memory _action)
+ external stoppable self
+ returns (bool)
+ {
+ // Prevent transactions to network contracts
+ require(_to != address(this), "colony-cannot-target-self");
+ require(_to != colonyNetworkAddress, "colony-cannot-target-network");
+ require(_to != tokenLockingAddress, "colony-cannot-target-token-locking");
+
+ // Prevent transactions to transfer held tokens
+ bytes4 sig;
+ assembly { sig := mload(add(_action, 0x20)) }
+
+ if (sig == APPROVE_SIG) { approveTransactionPreparation(_to, _action); }
+ else if (sig == BURN_SIG) { burnTransactionPreparation(_to, _action); }
+ else if (sig == TRANSFER_SIG) { transferTransactionPreparation(_to, _action); }
+ else if (sig == BURN_GUY_SIG || sig == TRANSFER_FROM_SIG) { burnGuyOrTransferFromTransactionPreparation(_action); }
+
+ // Prevent transactions to network-managed extensions installed in this colony
+ require(isContract(_to), "colony-to-must-be-contract");
+ // slither-disable-next-line unused-return
+ try ColonyExtension(_to).identifier() returns (bytes32 extensionId) {
+ require(
+ IColonyNetwork(colonyNetworkAddress).getExtensionInstallation(extensionId, address(this)) != _to,
+ "colony-cannot-target-extensions"
+ );
+ } catch {}
+
+ bool res = executeCall(_to, 0, _action);
+
+ if (sig == APPROVE_SIG) { approveTransactionCleanup(_to, _action); }
+
+ return res;
+ }
+
+ function approveTransactionPreparation(address _to, bytes memory _action) internal {
+ address spender;
+ assembly {
+ spender := mload(add(_action, 0x24))
+ }
+ updateApprovalAmountInternal(_to, spender, false);
+ }
+
+ function approveTransactionCleanup(address _to, bytes memory _action) internal {
+ address spender;
+ assembly {
+ spender := mload(add(_action, 0x24))
+ }
+ updateApprovalAmountInternal(_to, spender, true);
+ }
+
+ function burnTransactionPreparation(address _to, bytes memory _action) internal {
+ uint256 amount;
+ assembly {
+ amount := mload(add(_action, 0x24))
+ }
+ fundingPots[1].balance[_to] = sub(fundingPots[1].balance[_to], amount);
+ require(fundingPots[1].balance[_to] >= tokenApprovalTotals[_to], "colony-not-enough-tokens");
+ }
+
+ function transferTransactionPreparation(address _to, bytes memory _action) internal {
+ uint256 amount;
+ assembly {
+ amount := mload(add(_action, 0x44))
+ }
+ fundingPots[1].balance[_to] = sub(fundingPots[1].balance[_to], amount);
+ require(fundingPots[1].balance[_to] >= tokenApprovalTotals[_to], "colony-not-enough-tokens");
+ }
+
+ function burnGuyOrTransferFromTransactionPreparation(bytes memory _action) internal {
+ address spender;
+ assembly {
+ spender := mload(add(_action, 0x24))
+ }
+ require(spender != address(this), "colony-cannot-spend-own-allowance");
+ }
+
+ function updateApprovalAmount(address _token, address _spender) stoppable public {
+ updateApprovalAmountInternal(_token, _spender, false);
+ }
+
+ function updateApprovalAmountInternal(address _token, address _spender, bool _postApproval) internal {
+ uint256 recordedApproval = tokenApprovals[_token][_spender];
+ uint256 actualApproval = ERC20Extended(_token).allowance(address(this), _spender);
+ if (recordedApproval == actualApproval) {
+ return;
+ }
+
+ if (recordedApproval > actualApproval && !_postApproval){
+ // They've spend some tokens out of root. Adjust balances accordingly
+ // If we are post approval, then they have not spent tokens
+ fundingPots[1].balance[_token] = add(sub(fundingPots[1].balance[_token], recordedApproval), actualApproval);
+ }
+
+ tokenApprovalTotals[_token] = add(sub(tokenApprovalTotals[_token], recordedApproval), actualApproval);
+ require(fundingPots[1].balance[_token] >= tokenApprovalTotals[_token], "colony-approval-exceeds-balance");
+
+ tokenApprovals[_token][_spender] = actualApproval;
+ }
+
+}
\ No newline at end of file
diff --git a/contracts/colony/ColonyExpenditure.sol b/contracts/colony/ColonyExpenditure.sol
index 590aa4be98..3bbba7f024 100644
--- a/contracts/colony/ColonyExpenditure.sol
+++ b/contracts/colony/ColonyExpenditure.sol
@@ -34,7 +34,7 @@ contract ColonyExpenditure is ColonyStorage {
{
defaultGlobalClaimDelay = _defaultGlobalClaimDelay;
- emit ExpenditureGlobalClaimDelaySet(msg.sender, _defaultGlobalClaimDelay);
+ emit ExpenditureGlobalClaimDelaySet(msgSender(), _defaultGlobalClaimDelay);
}
function makeExpenditure(uint256 _permissionDomainId, uint256 _childSkillIndex, uint256 _domainId)
@@ -51,7 +51,7 @@ contract ColonyExpenditure is ColonyStorage {
expenditures[expenditureCount] = Expenditure({
status: ExpenditureStatus.Draft,
- owner: msg.sender,
+ owner: msgSender(),
fundingPotId: fundingPotCount,
domainId: _domainId,
finalizedTimestamp: 0,
@@ -59,7 +59,7 @@ contract ColonyExpenditure is ColonyStorage {
});
emit FundingPotAdded(fundingPotCount);
- emit ExpenditureAdded(msg.sender, expenditureCount);
+ emit ExpenditureAdded(msgSender(), expenditureCount);
return expenditureCount;
}
@@ -73,7 +73,7 @@ contract ColonyExpenditure is ColonyStorage {
{
expenditures[_id].owner = _newOwner;
- emit ExpenditureTransferred(msg.sender, _id, _newOwner);
+ emit ExpenditureTransferred(msgSender(), _id, _newOwner);
}
// Deprecated
@@ -91,7 +91,7 @@ contract ColonyExpenditure is ColonyStorage {
{
expenditures[_id].owner = _newOwner;
- emit ExpenditureTransferred(msg.sender, _id, _newOwner);
+ emit ExpenditureTransferred(msgSender(), _id, _newOwner);
}
function cancelExpenditure(uint256 _id)
@@ -103,7 +103,7 @@ contract ColonyExpenditure is ColonyStorage {
{
expenditures[_id].status = ExpenditureStatus.Cancelled;
- emit ExpenditureCancelled(msg.sender, _id);
+ emit ExpenditureCancelled(msgSender(), _id);
}
function lockExpenditure(uint256 _id)
@@ -115,7 +115,7 @@ contract ColonyExpenditure is ColonyStorage {
{
expenditures[_id].status = ExpenditureStatus.Locked;
- emit ExpenditureLocked(msg.sender, _id);
+ emit ExpenditureLocked(msgSender(), _id);
}
function finalizeExpenditure(uint256 _id)
@@ -131,7 +131,7 @@ contract ColonyExpenditure is ColonyStorage {
expenditures[_id].status = ExpenditureStatus.Finalized;
expenditures[_id].finalizedTimestamp = block.timestamp;
- emit ExpenditureFinalized(msg.sender, _id);
+ emit ExpenditureFinalized(msgSender(), _id);
}
function setExpenditureMetadata(uint256 _id, string memory _metadata)
@@ -141,7 +141,7 @@ contract ColonyExpenditure is ColonyStorage {
expenditureDraft(_id)
expenditureOnlyOwner(_id)
{
- emit ExpenditureMetadataSet(msg.sender, _id, _metadata);
+ emit ExpenditureMetadataSet(msgSender(), _id, _metadata);
}
function setExpenditureMetadata(
@@ -155,7 +155,7 @@ contract ColonyExpenditure is ColonyStorage {
expenditureExists(_id)
authDomain(_permissionDomainId, _childSkillIndex, expenditures[_id].domainId)
{
- emit ExpenditureMetadataSet(msg.sender, _id, _metadata);
+ emit ExpenditureMetadataSet(msgSender(), _id, _metadata);
}
function setExpenditureRecipients(uint256 _id, uint256[] memory _slots, address payable[] memory _recipients)
@@ -170,7 +170,7 @@ contract ColonyExpenditure is ColonyStorage {
for (uint256 i; i < _slots.length; i++) {
expenditureSlots[_id][_slots[i]].recipient = _recipients[i];
- emit ExpenditureRecipientSet(msg.sender, _id, _slots[i], _recipients[i]);
+ emit ExpenditureRecipientSet(msgSender(), _id, _slots[i], _recipients[i]);
}
}
@@ -200,7 +200,7 @@ contract ColonyExpenditure is ColonyStorage {
expenditureSlots[_id][_slots[i]].skills = new uint256[](1);
expenditureSlots[_id][_slots[i]].skills[0] = _skillIds[i];
- emit ExpenditureSkillSet(msg.sender, _id, _slots[i], _skillIds[i]);
+ emit ExpenditureSkillSet(msgSender(), _id, _slots[i], _skillIds[i]);
}
}
@@ -216,7 +216,7 @@ contract ColonyExpenditure is ColonyStorage {
for (uint256 i; i < _slots.length; i++) {
expenditureSlots[_id][_slots[i]].claimDelay = _claimDelays[i];
- emit ExpenditureClaimDelaySet(msg.sender, _id, _slots[i], _claimDelays[i]);
+ emit ExpenditureClaimDelaySet(msgSender(), _id, _slots[i], _claimDelays[i]);
}
}
@@ -232,7 +232,7 @@ contract ColonyExpenditure is ColonyStorage {
for (uint256 i; i < _slots.length; i++) {
expenditureSlots[_id][_slots[i]].payoutModifier = _payoutModifiers[i];
- emit ExpenditurePayoutModifierSet(msg.sender, _id, _slots[i], _payoutModifiers[i]);
+ emit ExpenditurePayoutModifierSet(msgSender(), _id, _slots[i], _payoutModifiers[i]);
}
}
diff --git a/contracts/colony/ColonyFunding.sol b/contracts/colony/ColonyFunding.sol
index 5f4cc1b4b0..d16f0f774f 100755
--- a/contracts/colony/ColonyFunding.sol
+++ b/contracts/colony/ColonyFunding.sol
@@ -25,12 +25,12 @@ import "./ColonyStorage.sol";
contract ColonyFunding is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
function lockToken() public stoppable onlyOwnExtension returns (uint256) {
uint256 lockId = ITokenLocking(tokenLockingAddress).lockToken(token);
- tokenLocks[msg.sender][lockId] = true;
+ tokenLocks[msgSender()][lockId] = true;
return lockId;
}
function unlockTokenForUser(address _user, uint256 _lockId) public stoppable onlyOwnExtension {
- require(tokenLocks[msg.sender][_lockId], "colony-bad-lock-id");
+ require(tokenLocks[msgSender()][_lockId], "colony-bad-lock-id");
ITokenLocking(tokenLockingAddress).unlockTokenForUser(token, _user, _lockId);
}
@@ -183,7 +183,7 @@ contract ColonyFunding is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
updatePayoutsWeCannotMakeAfterBudgetChange(payment.fundingPotId, _token, currentTotalAmount);
- emit PaymentPayoutSet(msg.sender, _id, _token, _amount);
+ emit PaymentPayoutSet(msgSender(), _id, _token, _amount);
}
function getFundingPotCount() public view returns (uint256 count) {
@@ -267,7 +267,7 @@ contract ColonyFunding is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
fundingPots[1].balance[_token] = add(fundingPots[1].balance[_token], remainder);
fundingPots[0].balance[_token] = add(fundingPots[0].balance[_token], feeToPay);
- emit ColonyFundsClaimed(msg.sender, _token, feeToPay, remainder);
+ emit ColonyFundsClaimed(msgSender(), _token, feeToPay, remainder);
}
function getNonRewardPotsTotal(address _token) public view returns (uint256) {
@@ -309,7 +309,7 @@ contract ColonyFunding is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
false
);
- emit RewardPayoutCycleStarted(msg.sender, totalLockCount);
+ emit RewardPayoutCycleStarted(msgSender(), totalLockCount);
}
// slither-disable-next-line reentrancy-no-eth
@@ -325,7 +325,7 @@ contract ColonyFunding is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
uint256 userReputation = checkReputation(
rewardPayoutCycles[_payoutId].reputationState,
domains[1].skillId,
- msg.sender,
+ msgSender(),
key,
value,
branchMask,
@@ -336,7 +336,7 @@ contract ColonyFunding is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
uint256 reward;
(tokenAddress, reward) = calculateRewardForUser(_payoutId, _squareRoots, userReputation);
- ITokenLocking(tokenLockingAddress).unlockTokenForUser(token, msg.sender, _payoutId);
+ ITokenLocking(tokenLockingAddress).unlockTokenForUser(token, msgSender(), _payoutId);
uint fee = calculateNetworkFeeForPayout(reward);
uint remainder = sub(reward, fee);
@@ -348,10 +348,10 @@ contract ColonyFunding is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
);
rewardPayoutCycles[_payoutId].amountRemaining = sub(rewardPayoutCycles[_payoutId].amountRemaining, reward);
- assert(ERC20Extended(tokenAddress).transfer(msg.sender, remainder));
+ assert(ERC20Extended(tokenAddress).transfer(msgSender(), remainder));
assert(ERC20Extended(tokenAddress).transfer(colonyNetworkAddress, fee));
- emit RewardPayoutClaimed(_payoutId, msg.sender, fee, remainder);
+ emit RewardPayoutClaimed(_payoutId, msgSender(), fee, remainder);
}
function finalizeRewardPayout(uint256 _payoutId) public stoppable {
@@ -363,7 +363,7 @@ contract ColonyFunding is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
rewardPayoutCycles[_payoutId].finalized = true;
pendingRewardPayments[payout.tokenAddress] = sub(pendingRewardPayments[payout.tokenAddress], payout.amountRemaining);
- emit RewardPayoutCycleEnded(msg.sender, _payoutId);
+ emit RewardPayoutCycleEnded(msgSender(), _payoutId);
}
function getRewardPayoutInfo(uint256 _payoutId) public view returns (RewardPayoutCycle memory rewardPayoutCycle) {
@@ -377,7 +377,7 @@ contract ColonyFunding is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
require(_rewardInverse > 0, "colony-reward-inverse-cannot-be-zero");
rewardInverse = _rewardInverse;
- emit ColonyRewardInverseSet(msg.sender, _rewardInverse);
+ emit ColonyRewardInverseSet(msgSender(), _rewardInverse);
}
function getRewardInverse() public view returns (uint256) {
@@ -422,7 +422,7 @@ contract ColonyFunding is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
// Checking if payout is active
require(block.timestamp - payout.blockTimestamp <= 60 days, "colony-reward-payout-not-active");
- uint256 userTokens = ITokenLocking(tokenLockingAddress).getUserLock(token, msg.sender).balance;
+ uint256 userTokens = ITokenLocking(tokenLockingAddress).getUserLock(token, msgSender()).balance;
require(userTokens > 0, "colony-reward-payout-invalid-user-tokens");
require(userReputation > 0, "colony-reward-payout-invalid-user-reputation");
@@ -509,7 +509,7 @@ contract ColonyFunding is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
nonRewardPotsTotal[_token] = sub(nonRewardPotsTotal[_token], _amount);
}
- emit ColonyFundsMovedBetweenFundingPots(msg.sender, _fromPot, _toPot, _amount, _token);
+ emit ColonyFundsMovedBetweenFundingPots(msgSender(), _fromPot, _toPot, _amount, _token);
}
@@ -584,7 +584,7 @@ contract ColonyFunding is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
fundingPot.payouts[_token] = add(sub(currentTotal, currentPayout), _amounts[i]);
- emit ExpenditurePayoutSet(msg.sender, _id, _slots[i], _token, _amounts[i]);
+ emit ExpenditurePayoutSet(msgSender(), _id, _slots[i], _token, _amounts[i]);
}
updatePayoutsWeCannotMakeAfterBudgetChange(expenditures[_id].fundingPotId, _token, currentTotal);
@@ -648,7 +648,7 @@ contract ColonyFunding is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
}
}
- emit PayoutClaimed(msg.sender, _fundingPotId, _token, remainder);
+ emit PayoutClaimed(msgSender(), _fundingPotId, _token, remainder);
}
function calculateNetworkFeeForPayout(uint256 _payout) private view returns (uint256 fee) {
@@ -663,14 +663,4 @@ contract ColonyFunding is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
}
}
- function burnTokens(address _token, uint256 _amount) public stoppable auth {
- // Check the root funding pot has enought
- require(fundingPots[1].balance[_token] >= _amount, "colony-not-enough-tokens");
- fundingPots[1].balance[_token] -= _amount;
-
- ERC20Extended(_token).burn(_amount);
-
- emit TokensBurned(msg.sender, _token, _amount);
- }
-
}
diff --git a/contracts/colony/ColonyPayment.sol b/contracts/colony/ColonyPayment.sol
index 0de466106f..f3dae35e20 100644
--- a/contracts/colony/ColonyPayment.sol
+++ b/contracts/colony/ColonyPayment.sol
@@ -56,16 +56,16 @@ contract ColonyPayment is ColonyStorage {
payments[paymentCount] = payment;
emit FundingPotAdded(fundingPotCount);
- emit PaymentAdded(msg.sender, paymentCount);
+ emit PaymentAdded(msgSender(), paymentCount);
if (_skillId > 0) {
setPaymentSkill(_permissionDomainId, _childSkillIndex, paymentCount, _skillId);
- emit PaymentSkillSet(msg.sender, paymentCount, _skillId);
+ emit PaymentSkillSet(msgSender(), paymentCount, _skillId);
}
- emit PaymentRecipientSet(msg.sender, paymentCount, _recipient);
- emit PaymentPayoutSet(msg.sender, paymentCount, _token, _amount);
+ emit PaymentRecipientSet(msgSender(), paymentCount, _recipient);
+ emit PaymentPayoutSet(msgSender(), paymentCount, _token, _amount);
return paymentCount;
}
@@ -92,7 +92,7 @@ contract ColonyPayment is ColonyStorage {
}
}
- emit PaymentFinalized(msg.sender, _id);
+ emit PaymentFinalized(msgSender(), _id);
}
function setPaymentRecipient(uint256 _permissionDomainId, uint256 _childSkillIndex, uint256 _id, address payable _recipient) public
@@ -103,7 +103,7 @@ contract ColonyPayment is ColonyStorage {
require(_recipient != address(0x0), "colony-payment-invalid-recipient");
payments[_id].recipient = _recipient;
- emit PaymentRecipientSet(msg.sender, _id, _recipient);
+ emit PaymentRecipientSet(msgSender(), _id, _recipient);
}
function setPaymentSkill(uint256 _permissionDomainId, uint256 _childSkillIndex, uint256 _id, uint256 _skillId) public
@@ -115,7 +115,7 @@ contract ColonyPayment is ColonyStorage {
{
payments[_id].skills[0] = _skillId;
- emit PaymentSkillSet(msg.sender, _id, _skillId);
+ emit PaymentSkillSet(msgSender(), _id, _skillId);
}
function getPayment(uint256 _id) public view returns (Payment memory) {
diff --git a/contracts/colony/ColonyRoles.sol b/contracts/colony/ColonyRoles.sol
index d38462bf73..bf18b3ab44 100644
--- a/contracts/colony/ColonyRoles.sol
+++ b/contracts/colony/ColonyRoles.sol
@@ -27,7 +27,7 @@ contract ColonyRoles is ColonyStorage, ContractRecoveryDataTypes {
function setRootRole(address _user, bool _setTo) public stoppable auth {
ColonyAuthority(address(authority)).setUserRole(_user, uint8(ColonyRole.Root), _setTo);
- emit ColonyRoleSet(msg.sender, _user, 1, uint8(ColonyRole.Root), _setTo);
+ emit ColonyRoleSet(msgSender(), _user, 1, uint8(ColonyRole.Root), _setTo);
}
function setArbitrationRole(
@@ -40,7 +40,7 @@ contract ColonyRoles is ColonyStorage, ContractRecoveryDataTypes {
{
ColonyAuthority(address(authority)).setUserRole(_user, _domainId, uint8(ColonyRole.Arbitration), _setTo);
- emit ColonyRoleSet(msg.sender, _user, _domainId, uint8(ColonyRole.Arbitration), _setTo);
+ emit ColonyRoleSet(msgSender(), _user, _domainId, uint8(ColonyRole.Arbitration), _setTo);
}
function setArchitectureRole(
@@ -53,7 +53,7 @@ contract ColonyRoles is ColonyStorage, ContractRecoveryDataTypes {
{
ColonyAuthority(address(authority)).setUserRole(_user, _domainId, uint8(ColonyRole.Architecture), _setTo);
- emit ColonyRoleSet(msg.sender, _user, _domainId, uint8(ColonyRole.Architecture), _setTo);
+ emit ColonyRoleSet(msgSender(), _user, _domainId, uint8(ColonyRole.Architecture), _setTo);
}
function setFundingRole(
@@ -66,7 +66,7 @@ contract ColonyRoles is ColonyStorage, ContractRecoveryDataTypes {
{
ColonyAuthority(address(authority)).setUserRole(_user, _domainId, uint8(ColonyRole.Funding), _setTo);
- emit ColonyRoleSet(msg.sender, _user, _domainId, uint8(ColonyRole.Funding), _setTo);
+ emit ColonyRoleSet(msgSender(), _user, _domainId, uint8(ColonyRole.Funding), _setTo);
}
function setAdministrationRole(
@@ -79,7 +79,7 @@ contract ColonyRoles is ColonyStorage, ContractRecoveryDataTypes {
{
ColonyAuthority(address(authority)).setUserRole(_user, _domainId, uint8(ColonyRole.Administration), _setTo);
- emit ColonyRoleSet(msg.sender, _user, _domainId, uint8(ColonyRole.Administration), _setTo);
+ emit ColonyRoleSet(msgSender(), _user, _domainId, uint8(ColonyRole.Administration), _setTo);
}
function setUserRoles(
@@ -112,7 +112,7 @@ contract ColonyRoles is ColonyStorage, ContractRecoveryDataTypes {
}
emit RecoveryRoleSet(_user, setTo);
}
- emit ColonyRoleSet(msg.sender, _user, _domainId, roleId, setTo);
+ emit ColonyRoleSet(msgSender(), _user, _domainId, roleId, setTo);
}
roles >>= 1;
diff --git a/contracts/colony/ColonyStorage.sol b/contracts/colony/ColonyStorage.sol
index 68189ed074..e38726c5e0 100755
--- a/contracts/colony/ColonyStorage.sol
+++ b/contracts/colony/ColonyStorage.sol
@@ -31,7 +31,7 @@ import "./ColonyDataTypes.sol";
// ignore-file-swc-108
-contract ColonyStorage is CommonStorage, ColonyDataTypes, ColonyNetworkDataTypes, DSMath {
+contract ColonyStorage is ColonyDataTypes, ColonyNetworkDataTypes, DSMath, CommonStorage {
uint256 constant COLONY_NETWORK_SLOT = 6;
// Storage
@@ -104,6 +104,9 @@ contract ColonyStorage is CommonStorage, ColonyDataTypes, ColonyNetworkDataTypes
uint256 defaultGlobalClaimDelay; // Storage slot 34
+ uint256 constant METATRANSACTION_NONCES_SLOT = 35;
+ mapping(address => uint256) metatransactionNonces; // Storage slot 35
+
// Constants
uint256 constant MAX_PAYOUT = 2**128 - 1; // 340,282,366,920,938,463,463 WADs
@@ -135,7 +138,7 @@ contract ColonyStorage is CommonStorage, ColonyDataTypes, ColonyNetworkDataTypes
modifier confirmTaskRoleIdentity(uint256 _id, TaskRole _role) {
Role storage role = tasks[_id].roles[uint8(_role)];
- require(msg.sender == role.user, "colony-task-role-identity-mismatch");
+ require(msgSender() == role.user, "colony-task-role-identity-mismatch");
_;
}
@@ -184,7 +187,7 @@ contract ColonyStorage is CommonStorage, ColonyDataTypes, ColonyNetworkDataTypes
}
modifier expenditureOnlyOwner(uint256 _id) {
- require(expenditures[_id].owner == msg.sender, "colony-expenditure-not-owner");
+ require(expenditures[_id].owner == msgSender(), "colony-expenditure-not-owner");
_;
}
@@ -234,30 +237,31 @@ contract ColonyStorage is CommonStorage, ColonyDataTypes, ColonyNetworkDataTypes
}
modifier self() {
- require(address(this) == msg.sender, "colony-not-self");
+ require(address(this) == msgSender(), "colony-not-self");
_;
}
modifier onlyOwnExtension() {
- require(isOwnExtension(msg.sender), "colony-must-be-own-extension");
+ require(isOwnExtension(msgSender()), "colony-must-be-own-extension");
+ assert(msgSender() == msg.sender);
_;
}
modifier auth override {
- require(isAuthorized(msg.sender, 1, msg.sig), "ds-auth-unauthorized");
+ require(isAuthorized(msgSender(), 1, msg.sig), "ds-auth-unauthorized");
_;
}
modifier authDomain(uint256 _permissionDomainId, uint256 _childSkillIndex, uint256 _childDomainId) {
require(domainExists(_permissionDomainId), "ds-auth-permission-domain-does-not-exist");
require(domainExists(_childDomainId), "ds-auth-child-domain-does-not-exist");
- require(isAuthorized(msg.sender, _permissionDomainId, msg.sig), "ds-auth-unauthorized");
+ require(isAuthorized(msgSender(), _permissionDomainId, msg.sig), "ds-auth-unauthorized");
require(validateDomainInheritance(_permissionDomainId, _childSkillIndex, _childDomainId), "ds-auth-invalid-domain-inheritence");
_;
}
modifier archSubdomain(uint256 _permissionDomainId, uint256 _childDomainId) {
- if (canCallOnlyBecauseArchitect(msg.sender, _permissionDomainId, msg.sig)) {
+ if (canCallOnlyBecauseArchitect(msgSender(), _permissionDomainId, msg.sig)) {
require(_permissionDomainId != _childDomainId, "ds-auth-only-authorized-in-child-domain");
}
_;
diff --git a/contracts/colony/ColonyTask.sol b/contracts/colony/ColonyTask.sol
index 015b4b37f1..e861a0454b 100755
--- a/contracts/colony/ColonyTask.sol
+++ b/contracts/colony/ColonyTask.sol
@@ -29,9 +29,9 @@ contract ColonyTask is ColonyStorage {
// Manager rated by worker
// Worker rated by evaluator
if (_role == TaskRole.Manager) {
- require(tasks[_id].roles[uint8(TaskRole.Worker)].user == msg.sender, "colony-user-cannot-rate-task-manager");
+ require(tasks[_id].roles[uint8(TaskRole.Worker)].user == msgSender(), "colony-user-cannot-rate-task-manager");
} else if (_role == TaskRole.Worker) {
- require(tasks[_id].roles[uint8(TaskRole.Evaluator)].user == msg.sender, "colony-user-cannot-rate-task-worker");
+ require(tasks[_id].roles[uint8(TaskRole.Evaluator)].user == msgSender(), "colony-user-cannot-rate-task-worker");
} else {
revert("colony-unsupported-role-to-rate");
}
@@ -111,8 +111,8 @@ contract ColonyTask is ColonyStorage {
tasks[taskCount].fundingPotId = fundingPotCount;
tasks[taskCount].domainId = _domainId;
tasks[taskCount].skills = new uint256[](1);
- tasks[taskCount].roles[uint8(TaskRole.Manager)].user = msg.sender;
- tasks[taskCount].roles[uint8(TaskRole.Evaluator)].user = msg.sender;
+ tasks[taskCount].roles[uint8(TaskRole.Manager)].user = msgSender();
+ tasks[taskCount].roles[uint8(TaskRole.Evaluator)].user = msgSender();
if (_skillId > 0) {
this.setTaskSkill(taskCount, _skillId);
@@ -127,7 +127,7 @@ contract ColonyTask is ColonyStorage {
this.setTaskDueDate(taskCount, dueDate);
emit FundingPotAdded(fundingPotCount);
- emit TaskAdded(msg.sender, taskCount);
+ emit TaskAdded(msgSender(), taskCount);
}
function getTaskCount() public view returns (uint256) {
@@ -289,7 +289,7 @@ contract ColonyTask is ColonyStorage {
require(rating != TaskRatings.None, "colony-task-rating-missing");
tasks[_id].roles[uint8(_role)].rating = rating;
- emit TaskWorkRatingRevealed(msg.sender, _id, _role, _rating);
+ emit TaskWorkRatingRevealed(msgSender(), _id, _role, _rating);
}
function generateSecret(bytes32 _salt, uint256 _value) public pure returns (bytes32) {
@@ -380,7 +380,7 @@ contract ColonyTask is ColonyStorage {
{
tasks[_id].deliverableHash = _deliverableHash;
markTaskCompleted(_id);
- emit TaskDeliverableSubmitted(msg.sender, _id, _deliverableHash);
+ emit TaskDeliverableSubmitted(msgSender(), _id, _deliverableHash);
}
function submitTaskDeliverableAndRating(uint256 _id, bytes32 _deliverableHash, bytes32 _ratingSecret) public
@@ -424,7 +424,7 @@ contract ColonyTask is ColonyStorage {
}
}
- emit TaskFinalized(msg.sender, _id);
+ emit TaskFinalized(msgSender(), _id);
}
function cancelTask(uint256 _id) public
@@ -467,7 +467,7 @@ contract ColonyTask is ColonyStorage {
function markTaskCompleted(uint256 _id) internal {
tasks[_id].completionTimestamp = block.timestamp;
- emit TaskCompleted(msg.sender, _id);
+ emit TaskCompleted(msgSender(), _id);
}
function updateReputation(TaskRole taskRole, Task storage task) internal {
diff --git a/contracts/colony/IColony.sol b/contracts/colony/IColony.sol
index c56760608c..0a5d43af99 100644
--- a/contracts/colony/IColony.sol
+++ b/contracts/colony/IColony.sol
@@ -19,12 +19,13 @@ pragma solidity >=0.7.3; // ignore-swc-103
pragma experimental ABIEncoderV2;
import "./../common/IRecovery.sol";
+import "./../common/IBasicMetaTransaction.sol";
import "./ColonyDataTypes.sol";
/// @title Colony interface
/// @notice All externally available functions are available here and registered to work with EtherRouter Network contract
-interface IColony is ColonyDataTypes, IRecovery {
+interface IColony is ColonyDataTypes, IRecovery, IBasicMetaTransaction {
// Implemented in DSAuth.sol
/// @notice Get the `ColonyAuthority` for the colony.
/// @return colonyAuthority The `ColonyAuthority` contract address
diff --git a/contracts/colonyNetwork/ColonyNetwork.sol b/contracts/colonyNetwork/ColonyNetwork.sol
index c8ea75bb08..ce15c6624e 100644
--- a/contracts/colonyNetwork/ColonyNetwork.sol
+++ b/contracts/colonyNetwork/ColonyNetwork.sol
@@ -20,7 +20,7 @@ pragma experimental "ABIEncoderV2";
import "./../common/EtherRouter.sol";
import "./../common/ERC20Extended.sol";
-import "./../common/MultiChain.sol";
+import "./../common/BasicMetaTransaction.sol";
import "./../colony/ColonyAuthority.sol";
import "./../colony/IColony.sol";
import "./../colony/IMetaColony.sol";
@@ -28,14 +28,14 @@ import "./../reputationMiningCycle/IReputationMiningCycle.sol";
import "./ColonyNetworkStorage.sol";
-contract ColonyNetwork is ColonyNetworkStorage, MultiChain {
+contract ColonyNetwork is BasicMetaTransaction, ColonyNetworkStorage {
// Meta Colony allowed to manage Global skills
// All colonies are able to manage their Local (domain associated) skills
modifier allowedToAddSkill(bool globalSkill) {
if (globalSkill) {
- require(msg.sender == metaColony, "colony-must-be-meta-colony");
+ require(msgSender() == metaColony, "colony-must-be-meta-colony");
} else {
- require(_isColony[msg.sender] || msg.sender == address(this), "colony-caller-must-be-colony");
+ require(_isColony[msgSender()] || msgSender() == address(this), "colony-caller-must-be-colony");
}
_;
}
@@ -107,21 +107,6 @@ contract ColonyNetwork is ColonyNetworkStorage, MultiChain {
return tokenLocking;
}
- function setMiningResolver(address _miningResolver) public
- stoppable
- auth
- {
- require(_miningResolver != address(0x0), "colony-mining-resolver-cannot-be-zero");
-
- miningCycleResolver = _miningResolver;
-
- emit MiningCycleResolverSet(_miningResolver);
- }
-
- function getMiningResolver() public view returns (address) {
- return miningCycleResolver;
- }
-
function createMetaColony(address _tokenAddress) public
stoppable
auth
@@ -315,7 +300,7 @@ contract ColonyNetwork is ColonyNetworkStorage, MultiChain {
_user,
_amount,
_skillId,
- msg.sender,
+ msgSender(),
nParents,
nChildren
);
@@ -349,6 +334,20 @@ contract ColonyNetwork is ColonyNetworkStorage, MultiChain {
emit TokenWhitelisted(_token, _status);
}
+ function getMetatransactionNonce(address _user) override public view returns (uint256 nonce){
+ return metatransactionNonces[_user];
+ }
+
+ function incrementMetatransactionNonce(address _user) override internal {
+ // We need to protect the metatransaction nonce slots, otherwise those with recovery
+ // permissions could replay metatransactions, which would be a disaster.
+ // What slot are we setting?
+ // This mapping is in slot 41 (see ColonyNetworkStorage.sol);
+ uint256 slot = uint256(keccak256(abi.encode(uint256(_user), uint256(METATRANSACTION_NONCES_SLOT))));
+ protectSlot(slot);
+ metatransactionNonces[_user] = add(metatransactionNonces[_user], 1);
+ }
+
function deployColony(address _tokenAddress, uint256 _version) internal returns (address) {
require(_tokenAddress != address(0x0), "colony-token-invalid-address");
require(colonyVersionResolver[_version] != address(0x00), "colony-network-invalid-version");
@@ -385,12 +384,12 @@ contract ColonyNetwork is ColonyNetworkStorage, MultiChain {
// Assign all permissions in root domain
IColony colony = IColony(_colonyAddress);
- colony.setRecoveryRole(msg.sender);
- colony.setRootRole(msg.sender, true);
- colony.setArbitrationRole(1, UINT256_MAX, msg.sender, 1, true);
- colony.setArchitectureRole(1, UINT256_MAX, msg.sender, 1, true);
- colony.setFundingRole(1, UINT256_MAX, msg.sender, 1, true);
- colony.setAdministrationRole(1, UINT256_MAX, msg.sender, 1, true);
+ colony.setRecoveryRole(msgSender());
+ colony.setRootRole(msgSender(), true);
+ colony.setArbitrationRole(1, UINT256_MAX, msgSender(), 1, true);
+ colony.setArchitectureRole(1, UINT256_MAX, msgSender(), 1, true);
+ colony.setFundingRole(1, UINT256_MAX, msgSender(), 1, true);
+ colony.setAdministrationRole(1, UINT256_MAX, msgSender(), 1, true);
// Colony will not have owner
DSAuth dsauth = DSAuth(_colonyAddress);
diff --git a/contracts/colonyNetwork/ColonyNetworkAuction.sol b/contracts/colonyNetwork/ColonyNetworkAuction.sol
index 6805ec1b96..1d41b95ee7 100644
--- a/contracts/colonyNetwork/ColonyNetworkAuction.sol
+++ b/contracts/colonyNetwork/ColonyNetworkAuction.sol
@@ -19,6 +19,7 @@ pragma solidity 0.7.3;
import "./ColonyNetworkStorage.sol";
import "./../common/MultiChain.sol";
+import "./../common/BasicMetaTransaction.sol";
contract ColonyNetworkAuction is ColonyNetworkStorage, MultiChain {
function startTokenAuction(address _token) public
@@ -59,7 +60,7 @@ contract ColonyNetworkAuction is ColonyNetworkStorage, MultiChain {
}
-contract DutchAuction is DSMath, MultiChain {
+contract DutchAuction is DSMath, MultiChain, BasicMetaTransaction {
address payable public colonyNetwork;
address public metaColonyAddress;
ERC20Extended public clnyToken;
@@ -81,6 +82,7 @@ contract DutchAuction is DSMath, MultiChain {
// Final price in CLNY per 10**18 Tokens (min 1, max 1e36)
uint public finalPrice;
bool public finalized;
+ mapping(address => uint256) metatransactionNonces;
mapping (address => uint256) public bids;
@@ -127,12 +129,20 @@ contract DutchAuction is DSMath, MultiChain {
constructor(address _clnyToken, address _token, address _metaColonyAddress) public {
require(_metaColonyAddress != address(0x0), "colony-auction-metacolony-cannot-be-zero");
- colonyNetwork = msg.sender;
+ colonyNetwork = msgSender();
metaColonyAddress = _metaColonyAddress;
clnyToken = ERC20Extended(_clnyToken);
token = ERC20Extended(_token);
}
+ function getMetatransactionNonce(address userAddress) override public view returns (uint256 nonce){
+ return metatransactionNonces[userAddress];
+ }
+
+ function incrementMetatransactionNonce(address user) override internal {
+ metatransactionNonces[user] = add(metatransactionNonces[user], 1);
+ }
+
function start() public
auctionNotStarted
{
@@ -207,16 +217,16 @@ contract DutchAuction is DSMath, MultiChain {
return;
}
- if (bids[msg.sender] == 0) {
+ if (bids[msgSender()] == 0) {
bidCount += 1;
}
- bids[msg.sender] = add(bids[msg.sender], amount);
+ bids[msgSender()] = add(bids[msgSender()], amount);
receivedTotal = add(receivedTotal, amount);
- require(clnyToken.transferFrom(msg.sender, address(this), amount), "colony-auction-bid-transfer-failed");
+ require(clnyToken.transferFrom(msgSender(), address(this), amount), "colony-auction-bid-transfer-failed");
- emit AuctionBid(msg.sender, amount, sub(_remainingToEndAuction, amount));
+ emit AuctionBid(msgSender(), amount, sub(_remainingToEndAuction, amount));
}
// Finalize the auction and set the final Token price
diff --git a/contracts/colonyNetwork/ColonyNetworkDataTypes.sol b/contracts/colonyNetwork/ColonyNetworkDataTypes.sol
index 2ef049b925..6ce7d711a3 100755
--- a/contracts/colonyNetwork/ColonyNetworkDataTypes.sol
+++ b/contracts/colonyNetwork/ColonyNetworkDataTypes.sol
@@ -137,6 +137,14 @@ interface ColonyNetworkDataTypes {
/// @param colony The address of the colony
event ExtensionUninstalled(bytes32 indexed extensionId, address indexed colony);
+ /// @notice Event logged when a token is deployed via transaction through the colony network
+ /// @param tokenAddress The address of the token deployed
+ event TokenDeployed(address tokenAddress);
+
+ /// @notice Event logged when a token authority is deployed via transaction through the colony network
+ /// @param tokenAuthorityAddress The address of the token authority deployed
+ event TokenAuthorityDeployed(address tokenAuthorityAddress);
+
struct Skill {
// total number of parent skills
uint128 nParents;
diff --git a/contracts/colonyNetwork/ColonyNetworkENS.sol b/contracts/colonyNetwork/ColonyNetworkENS.sol
index 50be5361ab..aa8f83503e 100644
--- a/contracts/colonyNetwork/ColonyNetworkENS.sol
+++ b/contracts/colonyNetwork/ColonyNetworkENS.sol
@@ -64,19 +64,19 @@ contract ColonyNetworkENS is ColonyNetworkStorage, MultiChain {
unowned(userNode, username)
{
require(bytes(username).length > 0, "colony-user-label-invalid");
- require(bytes(userLabels[msg.sender]).length == 0, "colony-user-label-already-owned");
+ require(bytes(userLabels[msgSender()]).length == 0, "colony-user-label-already-owned");
bytes32 subnode = keccak256(abi.encodePacked(username));
bytes32 node = keccak256(abi.encodePacked(userNode, subnode));
- userLabels[msg.sender] = username;
- records[node].addr = msg.sender;
+ userLabels[msgSender()] = username;
+ records[node].addr = msgSender();
records[node].orbitdb = orbitdb;
ENS(ens).setSubnodeOwner(userNode, subnode, address(this));
ENS(ens).setResolver(node, address(this));
- emit UserLabelRegistered(msg.sender, subnode);
+ emit UserLabelRegistered(msgSender(), subnode);
}
function registerColonyLabel(string memory colonyName, string memory orbitdb)
@@ -86,19 +86,19 @@ contract ColonyNetworkENS is ColonyNetworkStorage, MultiChain {
stoppable
{
require(bytes(colonyName).length > 0, "colony-colony-label-invalid");
- require(bytes(colonyLabels[msg.sender]).length == 0, "colony-already-labeled");
+ require(bytes(colonyLabels[msgSender()]).length == 0, "colony-already-labeled");
bytes32 subnode = keccak256(abi.encodePacked(colonyName));
bytes32 node = keccak256(abi.encodePacked(colonyNode, subnode));
- colonyLabels[msg.sender] = colonyName;
- records[node].addr = msg.sender;
+ colonyLabels[msgSender()] = colonyName;
+ records[node].addr = msgSender();
records[node].orbitdb = orbitdb;
ENS(ens).setSubnodeOwner(colonyNode, subnode, address(this));
ENS(ens).setResolver(node, address(this));
- emit ColonyLabelRegistered(msg.sender, subnode);
+ emit ColonyLabelRegistered(msgSender(), subnode);
}
function updateColonyOrbitDB(string memory orbitdb)
@@ -106,7 +106,7 @@ contract ColonyNetworkENS is ColonyNetworkStorage, MultiChain {
calledByColony
stoppable
{
- string storage label = colonyLabels[msg.sender];
+ string storage label = colonyLabels[msgSender()];
require(bytes(label).length > 0, "colony-colony-not-labeled");
bytes32 subnode = keccak256(abi.encodePacked(label));
bytes32 node = keccak256(abi.encodePacked(colonyNode, subnode));
@@ -118,7 +118,7 @@ contract ColonyNetworkENS is ColonyNetworkStorage, MultiChain {
notCalledByColony
stoppable
{
- string storage label = userLabels[msg.sender];
+ string storage label = userLabels[msgSender()];
require(bytes(label).length > 0, "colony-user-not-labeled");
bytes32 subnode = keccak256(abi.encodePacked(label));
bytes32 node = keccak256(abi.encodePacked(userNode, subnode));
diff --git a/contracts/colonyNetwork/ColonyNetworkExtensions.sol b/contracts/colonyNetwork/ColonyNetworkExtensions.sol
index 6c27b8b75b..dc4609b4d9 100644
--- a/contracts/colonyNetwork/ColonyNetworkExtensions.sol
+++ b/contracts/colonyNetwork/ColonyNetworkExtensions.sol
@@ -22,6 +22,8 @@ import "../colony/ColonyDataTypes.sol";
import "../colonyNetwork/IColonyNetwork.sol";
import "../extensions/ColonyExtension.sol";
import "./ColonyNetworkStorage.sol";
+import "./../metaTxToken/MetaTxToken.sol";
+import "./../common/TokenAuthority.sol";
contract ColonyNetworkExtensions is ColonyNetworkStorage {
@@ -52,15 +54,15 @@ contract ColonyNetworkExtensions is ColonyNetworkStorage {
calledByColony
{
require(resolvers[_extensionId][_version] != address(0x0), "colony-network-extension-bad-version");
- require(installations[_extensionId][msg.sender] == address(0x0), "colony-network-extension-already-installed");
+ require(installations[_extensionId][msgSender()] == address(0x0), "colony-network-extension-already-installed");
EtherRouter extension = new EtherRouter();
- installations[_extensionId][msg.sender] = address(extension);
+ installations[_extensionId][msgSender()] = address(extension);
extension.setResolver(resolvers[_extensionId][_version]);
- ColonyExtension(address(extension)).install(msg.sender);
+ ColonyExtension(address(extension)).install(msgSender());
- emit ExtensionInstalled(_extensionId, msg.sender, _version);
+ emit ExtensionInstalled(_extensionId, msgSender(), _version);
}
function upgradeExtension(bytes32 _extensionId, uint256 _newVersion)
@@ -68,9 +70,9 @@ contract ColonyNetworkExtensions is ColonyNetworkStorage {
stoppable
calledByColony
{
- require(installations[_extensionId][msg.sender] != address(0x0), "colony-network-extension-not-installed");
+ require(installations[_extensionId][msgSender()] != address(0x0), "colony-network-extension-not-installed");
- address payable extension = installations[_extensionId][msg.sender];
+ address payable extension = installations[_extensionId][msgSender()];
require(_newVersion == ColonyExtension(extension).version() + 1, "colony-network-extension-bad-increment");
require(resolvers[_extensionId][_newVersion] != address(0x0), "colony-network-extension-bad-version");
@@ -78,7 +80,7 @@ contract ColonyNetworkExtensions is ColonyNetworkStorage {
ColonyExtension(extension).finishUpgrade();
assert(ColonyExtension(extension).version() == _newVersion);
- emit ExtensionUpgraded(_extensionId, msg.sender, _newVersion);
+ emit ExtensionUpgraded(_extensionId, msgSender(), _newVersion);
}
function deprecateExtension(bytes32 _extensionId, bool _deprecated)
@@ -86,9 +88,9 @@ contract ColonyNetworkExtensions is ColonyNetworkStorage {
stoppable
calledByColony
{
- ColonyExtension(installations[_extensionId][msg.sender]).deprecate(_deprecated);
+ ColonyExtension(installations[_extensionId][msgSender()]).deprecate(_deprecated);
- emit ExtensionDeprecated(_extensionId, msg.sender, _deprecated);
+ emit ExtensionDeprecated(_extensionId, msgSender(), _deprecated);
}
function uninstallExtension(bytes32 _extensionId)
@@ -96,13 +98,13 @@ contract ColonyNetworkExtensions is ColonyNetworkStorage {
stoppable
calledByColony
{
- require(installations[_extensionId][msg.sender] != address(0x0), "colony-network-extension-not-installed");
+ require(installations[_extensionId][msgSender()] != address(0x0), "colony-network-extension-not-installed");
- ColonyExtension extension = ColonyExtension(installations[_extensionId][msg.sender]);
- installations[_extensionId][msg.sender] = address(0x0);
+ ColonyExtension extension = ColonyExtension(installations[_extensionId][msgSender()]);
+ installations[_extensionId][msgSender()] = address(0x0);
extension.uninstall();
- emit ExtensionUninstalled(_extensionId, msg.sender);
+ emit ExtensionUninstalled(_extensionId, msgSender());
}
// Public view functions
@@ -138,4 +140,25 @@ contract ColonyNetworkExtensions is ColonyNetworkStorage {
address extension = Resolver(_resolver).lookup(VERSION_SIG);
return ColonyExtension(extension).version();
}
+
+ function deployTokenViaNetwork(string memory _name, string memory _symbol, uint8 _decimals) public
+ stoppable
+ returns (address)
+ {
+ MetaTxToken token = new MetaTxToken(_name, _symbol, _decimals);
+ token.setOwner(msgSender());
+
+ emit TokenDeployed(address(token));
+ }
+
+ function deployTokenAuthority(address _token, address _colony, address[] memory allowedToTransfer) public
+ stoppable
+ returns (address)
+ {
+ TokenAuthority tokenAuthority = new TokenAuthority(_token, _colony, allowedToTransfer);
+
+ emit TokenAuthorityDeployed(address(tokenAuthority));
+ }
+
+
}
diff --git a/contracts/colonyNetwork/ColonyNetworkMining.sol b/contracts/colonyNetwork/ColonyNetworkMining.sol
index beab607517..bb456a5728 100644
--- a/contracts/colonyNetwork/ColonyNetworkMining.sol
+++ b/contracts/colonyNetwork/ColonyNetworkMining.sol
@@ -30,7 +30,7 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain {
// TODO: Can we handle a dispute regarding the very first hash that should be set?
modifier onlyReputationMiningCycle () {
- require(msg.sender == activeReputationMiningCycle, "colony-reputation-mining-sender-not-active-reputation-cycle");
+ require(msgSender() == activeReputationMiningCycle, "colony-reputation-mining-sender-not-active-reputation-cycle");
_;
}
@@ -227,21 +227,21 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain {
function stakeForMining(uint256 _amount) public stoppable {
address clnyToken = IMetaColony(metaColony).getToken();
- uint256 existingObligation = ITokenLocking(tokenLocking).getObligation(msg.sender, clnyToken, address(this));
+ uint256 existingObligation = ITokenLocking(tokenLocking).getObligation(msgSender(), clnyToken, address(this));
- ITokenLocking(tokenLocking).approveStake(msg.sender, _amount, clnyToken);
- ITokenLocking(tokenLocking).obligateStake(msg.sender, _amount, clnyToken);
+ ITokenLocking(tokenLocking).approveStake(msgSender(), _amount, clnyToken);
+ ITokenLocking(tokenLocking).obligateStake(msgSender(), _amount, clnyToken);
- miningStakes[msg.sender].timestamp = getNewTimestamp(existingObligation, _amount, miningStakes[msg.sender].timestamp, block.timestamp);
- miningStakes[msg.sender].amount = add(miningStakes[msg.sender].amount, _amount);
+ miningStakes[msgSender()].timestamp = getNewTimestamp(existingObligation, _amount, miningStakes[msgSender()].timestamp, block.timestamp);
+ miningStakes[msgSender()].amount = add(miningStakes[msgSender()].amount, _amount);
}
function unstakeForMining(uint256 _amount) public stoppable {
address clnyToken = IMetaColony(metaColony).getToken();
// Prevent those involved in a mining cycle withdrawing stake during the mining process.
- require(!IReputationMiningCycle(activeReputationMiningCycle).userInvolvedInMiningCycle(msg.sender), "colony-network-hash-submitted");
- ITokenLocking(tokenLocking).deobligateStake(msg.sender, _amount, clnyToken);
- miningStakes[msg.sender].amount = sub(miningStakes[msg.sender].amount, _amount);
+ require(!IReputationMiningCycle(activeReputationMiningCycle).userInvolvedInMiningCycle(msgSender()), "colony-network-hash-submitted");
+ ITokenLocking(tokenLocking).deobligateStake(msgSender(), _amount, clnyToken);
+ miningStakes[msgSender()].amount = sub(miningStakes[msgSender()].amount, _amount);
}
function getMiningStake(address _user) public stoppable returns (MiningStake memory) {
@@ -287,4 +287,19 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain {
return add(mul(prevWeight, _prevTime), mul(currWeight, _currTime)) / add(prevWeight, currWeight);
}
+
+ function setMiningResolver(address _miningResolver) public
+ stoppable
+ auth
+ {
+ require(_miningResolver != address(0x0), "colony-mining-resolver-cannot-be-zero");
+
+ miningCycleResolver = _miningResolver;
+
+ emit MiningCycleResolverSet(_miningResolver);
+ }
+
+ function getMiningResolver() public view returns (address) {
+ return miningCycleResolver;
+ }
}
diff --git a/contracts/colonyNetwork/ColonyNetworkStorage.sol b/contracts/colonyNetwork/ColonyNetworkStorage.sol
index dc846174e3..45d05b341a 100644
--- a/contracts/colonyNetwork/ColonyNetworkStorage.sol
+++ b/contracts/colonyNetwork/ColonyNetworkStorage.sol
@@ -27,7 +27,7 @@ import "./ColonyNetworkDataTypes.sol";
// ignore-file-swc-108
-contract ColonyNetworkStorage is CommonStorage, ColonyNetworkDataTypes, DSMath {
+contract ColonyNetworkStorage is ColonyNetworkDataTypes, DSMath, CommonStorage {
// Number of colonies in the network
uint256 colonyCount; // Storage slot 6
// uint256 version number of the latest deployed Colony contract, used in creating new colonies
@@ -100,18 +100,24 @@ contract ColonyNetworkStorage is CommonStorage, ColonyNetworkDataTypes, DSMath {
// Used for whitelisting payout tokens
mapping (address => bool) payoutWhitelist; // Storage slot 40
+ uint256 constant METATRANSACTION_NONCES_SLOT = 41;
+ mapping(address => uint256) metatransactionNonces; // Storage slot 41
+
+
modifier calledByColony() {
- require(_isColony[msg.sender], "colony-caller-must-be-colony");
+ require(_isColony[msgSender()], "colony-caller-must-be-colony");
+ assert(msgSender() == msg.sender);
_;
}
modifier notCalledByColony() {
- require(!_isColony[msg.sender], "colony-caller-must-not-be-colony");
+ require(!_isColony[msgSender()], "colony-caller-must-not-be-colony");
_;
}
modifier calledByMetaColony() {
- require(msg.sender == metaColony, "colony-caller-must-be-meta-colony");
+ require(msgSender() == metaColony, "colony-caller-must-be-meta-colony");
+ assert(msgSender() == msg.sender);
_;
}
}
diff --git a/contracts/colonyNetwork/IColonyNetwork.sol b/contracts/colonyNetwork/IColonyNetwork.sol
index 3a5a696b9f..bc512481fc 100644
--- a/contracts/colonyNetwork/IColonyNetwork.sol
+++ b/contracts/colonyNetwork/IColonyNetwork.sol
@@ -19,12 +19,14 @@ pragma solidity >=0.7.3; // ignore-swc-103
pragma experimental "ABIEncoderV2";
import "./../common/IRecovery.sol";
+import "./../common/IBasicMetaTransaction.sol";
+
import "./ColonyNetworkDataTypes.sol";
/// @title Colony Network interface
/// @notice All externally available functions are available here and registered to work with EtherRouter Network contract
-interface IColonyNetwork is ColonyNetworkDataTypes, IRecovery {
+interface IColonyNetwork is ColonyNetworkDataTypes, IRecovery, IBasicMetaTransaction {
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in ERC-165
@@ -403,4 +405,21 @@ interface IColonyNetwork is ColonyNetworkDataTypes, IRecovery {
/// @notice Called to get the total per-cycle reputation mining reward.
/// @return The CLNY awarded per mining cycle to the miners.
function getReputationMiningCycleReward() external view returns (uint256);
+
+ /// @notice Called to deploy a token.
+ /// @dev This is more expensive than deploying a token directly, but is able to be done via
+ /// a metatransaction
+ /// @param _name The name of the token
+ /// @param _symbol The short 'ticket' symbol for the token
+ /// @param _decimals The number of decimal places that 1 user-facing token can be divided up in to
+ /// In the case of ETH, and most tokens, this is 18.
+ function deployTokenViaNetwork(string memory _name, string memory _symbol, uint8 _decimals) external returns (address);
+
+ /// @notice Called to deploy a token authority
+ /// @dev This is more expensive than deploying a token directly, but is able to be done via
+ /// a metatransaction
+ /// @param _token The address of the otken
+ /// @param _colony The address of the colony in control of the token
+ /// @param allowedToTransfer An array of addresses that are allowed to transfer the token even if it's locked
+ function deployTokenAuthority(address _token, address _colony, address[] memory allowedToTransfer) external returns (address);
}
diff --git a/contracts/common/BasicMetaTransaction.sol b/contracts/common/BasicMetaTransaction.sol
new file mode 100644
index 0000000000..02182629d6
--- /dev/null
+++ b/contracts/common/BasicMetaTransaction.sol
@@ -0,0 +1,54 @@
+pragma solidity 0.7.3;
+
+import "../../lib/dappsys/math.sol";
+import "./MetaTransactionMsgSender.sol";
+import "./MultiChain.sol";
+
+abstract contract BasicMetaTransaction is DSMath, MetaTransactionMsgSender, MultiChain {
+
+ event MetaTransactionExecuted(address user, address payable relayerAddress, bytes functionSignature);
+
+ function getMetatransactionNonce(address _user) public view virtual returns (uint256 nonce);
+
+ // NB if implementing this functionality in a contract with recovery mode,
+ // you MUST prevent the metatransaction nonces from being editable with recovery mode.
+ function incrementMetatransactionNonce(address _user) internal virtual;
+
+ /// @notice Main function to be called when user wants to execute meta transaction.
+ /// The actual function to be called should be passed as param with name functionSignature
+ /// Here the basic signature recovery is being used. Signature is expected to be generated using
+ /// personal_sign method.
+ /// @param _user Address of user trying to do meta transaction
+ /// @param _payload Function call to make via meta transaction
+ /// @param _sigR R part of the signature
+ /// @param _sigS S part of the signature
+ /// @param _sigV V part of the signature
+ // slither-disable-next-line locked-ether
+ function executeMetaTransaction(address _user, bytes memory _payload,
+ bytes32 _sigR, bytes32 _sigS, uint8 _sigV) public payable returns (bytes memory) {
+
+ require(verify(_user, getMetatransactionNonce(_user), getChainId(), _payload, _sigR, _sigS, _sigV), "metatransaction-signer-signature-mismatch");
+ incrementMetatransactionNonce(_user);
+
+ // Append _user at the end to extract it from calling context
+ (bool success, bytes memory returnData) = address(this).call(abi.encodePacked(_payload, METATRANSACTION_FLAG, _user));
+ require(success, "colony-metatx-function-call-unsuccessful");
+
+ emit MetaTransactionExecuted(_user, msgSender(), _payload);
+ return returnData;
+ }
+
+ // Builds a prefixed hash to mimic the behavior of eth_sign.
+ function prefixed(bytes32 _hash) internal pure returns (bytes32) {
+ return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash));
+ }
+
+ function verify(address _owner, uint256 _nonce, uint256 _chainId, bytes memory _payload,
+ bytes32 _sigR, bytes32 _sigS, uint8 _sigV) public view returns (bool) {
+
+ bytes32 hash = prefixed(keccak256(abi.encodePacked(_nonce, this, _chainId, _payload)));
+ address signer = ecrecover(hash, _sigV, _sigR, _sigS);
+ require(signer != address(0), "colony-metatx-invalid-signature");
+ return (_owner == signer);
+ }
+}
\ No newline at end of file
diff --git a/contracts/common/CommonStorage.sol b/contracts/common/CommonStorage.sol
index 96de8060ac..633766a33e 100644
--- a/contracts/common/CommonStorage.sol
+++ b/contracts/common/CommonStorage.sol
@@ -18,18 +18,29 @@
pragma solidity 0.7.3;
import "./../../lib/dappsys/auth.sol";
+import "./../common/MetaTransactionMsgSender.sol";
// ignore-file-swc-131
// ignore-file-swc-108
-contract CommonStorage is DSAuth {
+abstract contract CommonStorage is DSAuth, MetaTransactionMsgSender {
uint256 constant UINT256_MAX = 2**256 - 1;
uint256 constant AUTHORITY_SLOT = 0;
uint256 constant OWNER_SLOT = 1;
uint256 constant RESOLVER_SLOT = 2;
+ bytes32 constant PROTECTED = keccak256("Recovery Mode Protected Slot");
+
+ function protectSlot(uint256 _slot) internal always {
+ uint256 flagSlot = uint256(keccak256(abi.encodePacked("RECOVERY_PROTECTED", _slot)));
+ uint256 protectFlag = uint256(PROTECTED);
+ assembly {
+ sstore(flagSlot, protectFlag) // ignore-swc-124
+ }
+ }
+
// Address of the Resolver contract used by EtherRouter for lookups and routing
address resolver; // Storage slot 2 (from DSAuth there is authority and owner at storage slots 0 and 1 respectively)
diff --git a/contracts/common/ContractRecovery.sol b/contracts/common/ContractRecovery.sol
index 415ad5821c..01b83fd14a 100644
--- a/contracts/common/ContractRecovery.sol
+++ b/contracts/common/ContractRecovery.sol
@@ -34,6 +34,14 @@ contract ContractRecovery is ContractRecoveryDataTypes, CommonStorage { // ignor
require(_slot != OWNER_SLOT, "colony-common-protected-variable");
require(_slot != RESOLVER_SLOT, "colony-common-protected-variable");
+ bytes32 flag;
+ uint256 flagSlot = uint256(keccak256(abi.encodePacked("RECOVERY_PROTECTED", _slot)));
+ assembly {
+ flag := sload(flagSlot)
+ }
+
+ require(flag != PROTECTED, "colony-protected-variable");
+
// NB. This isn't necessarily a colony - could be ColonyNetwork. But they both have this function, so it's okay.
IRecovery(address(this)).checkNotAdditionalProtectedVariable(_slot); // ignore-swc-123
@@ -50,6 +58,9 @@ contract ContractRecovery is ContractRecoveryDataTypes, CommonStorage { // ignor
sstore(x, y) // ignore-swc-124
}
+ // Make sure we're not trying to change a flag protecting something else
+ require(oldValue != PROTECTED, "colony-protected-variable");
+
// Restore key variables
recoveryRolesCount = _recoveryRolesCount;
@@ -58,7 +69,7 @@ contract ContractRecovery is ContractRecoveryDataTypes, CommonStorage { // ignor
recoveryApprovalCount = 0;
recoveryEditedTimestamp = block.timestamp;
- emit RecoveryStorageSlotSet(msg.sender, _slot, oldValue, _value);
+ emit RecoveryStorageSlotSet(msgSender(), _slot, oldValue, _value);
}
function isInRecoveryMode() public view returns (bool) {
@@ -70,15 +81,15 @@ contract ContractRecovery is ContractRecoveryDataTypes, CommonStorage { // ignor
recoveryApprovalCount = 0;
recoveryEditedTimestamp = block.timestamp;
- emit RecoveryModeEntered(msg.sender);
+ emit RecoveryModeEntered(msgSender());
}
function approveExitRecovery() public recovery auth {
- require(recoveryApprovalTimestamps[msg.sender] < recoveryEditedTimestamp, "colony-recovery-approval-already-given"); // ignore-swc-116
- recoveryApprovalTimestamps[msg.sender] = block.timestamp;
+ require(recoveryApprovalTimestamps[msgSender()] < recoveryEditedTimestamp, "colony-recovery-approval-already-given"); // ignore-swc-116
+ recoveryApprovalTimestamps[msgSender()] = block.timestamp;
recoveryApprovalCount++;
- emit RecoveryModeExitApproved(msg.sender);
+ emit RecoveryModeExitApproved(msgSender());
}
function exitRecoveryMode() public recovery auth {
@@ -91,7 +102,7 @@ contract ContractRecovery is ContractRecoveryDataTypes, CommonStorage { // ignor
require(recoveryApprovalCount >= numRequired, "colony-recovery-exit-insufficient-approvals");
recoveryMode = false;
- emit RecoveryModeExited(msg.sender);
+ emit RecoveryModeExited(msgSender());
}
// Can only be called by the root role.
diff --git a/contracts/common/IBasicMetaTransaction.sol b/contracts/common/IBasicMetaTransaction.sol
new file mode 100644
index 0000000000..ae005d4bb8
--- /dev/null
+++ b/contracts/common/IBasicMetaTransaction.sol
@@ -0,0 +1,21 @@
+pragma solidity 0.7.3;
+
+
+interface IBasicMetaTransaction {
+
+ event MetaTransactionExecuted(address userAddress, address payable relayerAddress, bytes payload);
+
+ /// @notice Executes a metatransaction targeting this contract
+ /// @param userAddress The address of the user that signed the metatransaction
+ /// @param payload The transaction data that will be executed if signature valid
+ /// @param sigR The 'r' part of the signature
+ /// @param sigS The 's' part of the signature
+ /// @param sigV The 'v' part of the signature
+ function executeMetaTransaction(address userAddress, bytes memory payload,
+ bytes32 sigR, bytes32 sigS, uint8 sigV) external payable returns(bytes memory);
+
+ /// @notice Gets the next metatransaction nonce for user that should be used targeting this contract
+ /// @param userAddress The address of the user that will sign the metatransaction
+ function getMetatransactionNonce(address userAddress) external view returns(uint256 nonce);
+
+}
diff --git a/contracts/common/MetaTransactionMsgSender.sol b/contracts/common/MetaTransactionMsgSender.sol
new file mode 100644
index 0000000000..c41999ed1c
--- /dev/null
+++ b/contracts/common/MetaTransactionMsgSender.sol
@@ -0,0 +1,28 @@
+pragma solidity 0.7.3;
+
+import "../../lib/dappsys/math.sol";
+
+abstract contract MetaTransactionMsgSender is DSMath {
+
+ bytes32 constant METATRANSACTION_FLAG = keccak256("METATRANSACTION");
+
+ function msgSender() internal view returns(address payable sender) {
+ uint256 index = msg.data.length;
+ if(msg.sender == address(this) && index >= 52) {
+ bytes memory array = msg.data;
+ bytes32 flag;
+ assembly {
+ flag := mload(add(array, sub(index, 20)))
+ }
+ if (flag != METATRANSACTION_FLAG){
+ return msg.sender;
+ }
+ assembly {
+ // Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those.
+ sender := and(mload(add(array, index)), 0xffffffffffffffffffffffffffffffffffffffff)
+ }
+ } else {
+ return msg.sender;
+ }
+ }
+}
\ No newline at end of file
diff --git a/contracts/common/TokenAuthority.sol b/contracts/common/TokenAuthority.sol
new file mode 100644
index 0000000000..b33c759934
--- /dev/null
+++ b/contracts/common/TokenAuthority.sol
@@ -0,0 +1,59 @@
+/*
+ This file is part of The Colony Network.
+
+ The Colony Network is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ The Colony Network is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with The Colony Network. If not, see .
+*/
+
+pragma solidity 0.7.3;
+
+import "../../lib/dappsys/auth.sol";
+
+
+contract TokenAuthority is DSAuthority {
+ address public token;
+ mapping(address => mapping(bytes4 => bool)) authorizations;
+
+ bytes4 constant BURN_FUNC_SIG = bytes4(keccak256("burn(uint256)"));
+ bytes4 constant BURN_OVERLOAD_FUNC_SIG = bytes4(keccak256("burn(address,uint256)"));
+
+ constructor(address _token, address _colony, address[] memory allowedToTransfer) {
+ token = _token;
+ bytes4 transferSig = bytes4(keccak256("transfer(address,uint256)"));
+ bytes4 transferFromSig = bytes4(keccak256("transferFrom(address,address,uint256)"));
+ bytes4 mintSig = bytes4(keccak256("mint(uint256)"));
+ bytes4 mintSigOverload = bytes4(keccak256("mint(address,uint256)"));
+
+ authorizations[_colony][transferSig] = true;
+ authorizations[_colony][mintSig] = true;
+ authorizations[_colony][mintSigOverload] = true;
+
+ for (uint i = 0; i < allowedToTransfer.length; i++) {
+ authorizations[allowedToTransfer[i]][transferSig] = true;
+ authorizations[allowedToTransfer[i]][transferFromSig] = true;
+ }
+ }
+
+ function canCall(address src, address dst, bytes4 sig) public view override returns (bool) {
+ if (sig == BURN_FUNC_SIG || sig == BURN_OVERLOAD_FUNC_SIG) {
+ // We allow anyone to burn their own tokens even when the token is still locked
+ return true;
+ }
+
+ if (dst != token) {
+ return false;
+ }
+
+ return authorizations[src][sig];
+ }
+}
diff --git a/contracts/extensions/CoinMachine.sol b/contracts/extensions/CoinMachine.sol
index 155c25d80e..c1b3c6cfb5 100644
--- a/contracts/extensions/CoinMachine.sol
+++ b/contracts/extensions/CoinMachine.sol
@@ -65,10 +65,19 @@ contract CoinMachine is ColonyExtension {
uint256 soldTotal; // Total tokens sold by the coin machine
mapping(address => uint256) soldUser; // Tokens sold to a particular user
+ mapping(address => uint256) metatransactionNonces;
+ function getMetatransactionNonce(address userAddress) override public view returns (uint256 nonce){
+ return metatransactionNonces[userAddress];
+ }
+
+ function incrementMetatransactionNonce(address user) override internal {
+ metatransactionNonces[user]++;
+ }
+
// Modifiers
modifier onlyRoot() {
- require(colony.hasUserRole(msg.sender, 1, ColonyDataTypes.ColonyRole.Root), "coin-machine-caller-not-root");
+ require(colony.hasUserRole(msgSender(), 1, ColonyDataTypes.ColonyRole.Root), "coin-machine-caller-not-root");
_;
}
@@ -196,7 +205,7 @@ contract CoinMachine is ColonyExtension {
updatePeriod();
require(
- whitelist == address(0x0) || Whitelist(whitelist).isApproved(msg.sender),
+ whitelist == address(0x0) || Whitelist(whitelist).isApproved(msgSender()),
"coin-machine-unauthorised"
);
@@ -222,15 +231,15 @@ contract CoinMachine is ColonyExtension {
if (purchaseToken == address(0x0)) {
require(msg.value >= totalCost, "coin-machine-insufficient-funds");
- if (msg.value > totalCost) { msg.sender.transfer(msg.value - totalCost); } // Refund any balance
+ if (msg.value > totalCost) { msgSender().transfer(msg.value - totalCost); } // Refund any balance
payable(address(colony)).transfer(totalCost);
} else {
- require(ERC20(purchaseToken).transferFrom(msg.sender, address(colony), totalCost), "coin-machine-purchase-failed");
+ require(ERC20(purchaseToken).transferFrom(msgSender(), address(colony), totalCost), "coin-machine-purchase-failed");
}
- require(ERC20(token).transfer(msg.sender, numTokens), "coin-machine-transfer-failed");
+ require(ERC20(token).transfer(msgSender(), numTokens), "coin-machine-transfer-failed");
- emit TokensBought(msg.sender, numTokens, totalCost);
+ emit TokensBought(msgSender(), numTokens, totalCost);
}
/// @notice Bring the token accounting current
diff --git a/contracts/extensions/ColonyExtension.sol b/contracts/extensions/ColonyExtension.sol
index c9ec705d99..3570228c51 100644
--- a/contracts/extensions/ColonyExtension.sol
+++ b/contracts/extensions/ColonyExtension.sol
@@ -22,9 +22,9 @@ import "./../../lib/dappsys/math.sol";
import "./../common/EtherRouter.sol";
import "./../colony/IColony.sol";
import "./../colony/ColonyDataTypes.sol";
+import "./../common/BasicMetaTransaction.sol";
-
-abstract contract ColonyExtension is DSAuth, DSMath {
+abstract contract ColonyExtension is DSAuth, DSMath, BasicMetaTransaction {
uint256 constant UINT256_MAX = 2**256 - 1;
diff --git a/contracts/extensions/EvaluatedExpenditure.sol b/contracts/extensions/EvaluatedExpenditure.sol
index 6f41b4e74c..b4cbaea803 100644
--- a/contracts/extensions/EvaluatedExpenditure.sol
+++ b/contracts/extensions/EvaluatedExpenditure.sol
@@ -29,6 +29,7 @@ contract EvaluatedExpenditure is ColonyExtension {
uint256 constant PAYOUT_MODIFIER_OFFSET = 2;
bool constant MAPPING = false;
bool constant ARRAY = true;
+ mapping(address => uint256) metatransactionNonces;
/// @notice Returns the identifier of the extension
function identifier() public override pure returns (bytes32) {
@@ -61,6 +62,14 @@ contract EvaluatedExpenditure is ColonyExtension {
selfdestruct(address(uint160(address(colony))));
}
+ function getMetatransactionNonce(address _userAddress) override public view returns (uint256 nonce){
+ return metatransactionNonces[_userAddress];
+ }
+
+ function incrementMetatransactionNonce(address _user) override internal {
+ metatransactionNonces[_user] = add(metatransactionNonces[_user], 1);
+ }
+
/// @notice Sets the payout modifiers in given expenditure slots, using the arbitration permission
/// @param _permissionDomainId The domainId in which the extension has the arbitration permission
/// @param _childSkillIndex The index that the `_domainId` is relative to `_permissionDomainId`
diff --git a/contracts/extensions/FundingQueue.sol b/contracts/extensions/FundingQueue.sol
index c70c948c7b..674cd38a09 100644
--- a/contracts/extensions/FundingQueue.sol
+++ b/contracts/extensions/FundingQueue.sol
@@ -71,6 +71,14 @@ contract FundingQueue is ColonyExtension, PatriciaTreeProofs {
mapping (uint256 => mapping (address => uint256)) supporters;
// Technically a circular singly-linked list
mapping (uint256 => uint256) queue; // proposalId => nextProposalId
+ mapping(address => uint256) metatransactionNonces;
+ function getMetatransactionNonce(address userAddress) override public view returns (uint256 nonce){
+ return metatransactionNonces[userAddress];
+ }
+
+ function incrementMetatransactionNonce(address user) override internal {
+ metatransactionNonces[user]++;
+ }
// Public functions
@@ -143,7 +151,7 @@ contract FundingQueue is ColonyExtension, PatriciaTreeProofs {
proposalCount++;
proposals[proposalCount] = Proposal(
ProposalState.Inactive,
- msg.sender,
+ msgSender(),
_token,
_domainId,
0,
@@ -166,7 +174,7 @@ contract FundingQueue is ColonyExtension, PatriciaTreeProofs {
require(proposal.state != ProposalState.Cancelled, "funding-queue-already-cancelled");
require(proposal.state != ProposalState.Completed, "funding-queue-already-completed");
- require(proposal.creator == msg.sender, "funding-queue-not-creator");
+ require(proposal.creator == msgSender(), "funding-queue-not-creator");
require(queue[_prevId] == _id, "funding-queue-bad-prev-id");
proposal.state = ProposalState.Cancelled;
@@ -192,13 +200,13 @@ contract FundingQueue is ColonyExtension, PatriciaTreeProofs {
Proposal storage proposal = proposals[_id];
require(proposal.state == ProposalState.Inactive, "funding-queue-not-inactive");
- require(proposal.creator == msg.sender, "funding-queue-not-creator");
+ require(proposal.creator == msgSender(), "funding-queue-not-creator");
proposal.state = ProposalState.Active;
proposal.domainTotalRep = checkReputation(_id, address(0x0), _key, _value, _branchMask, _siblings);
uint256 stake = wmul(proposal.domainTotalRep, STAKE_FRACTION);
- colony.obligateStake(msg.sender, proposal.domainId, stake);
+ colony.obligateStake(msgSender(), proposal.domainId, stake);
emit ProposalStaked(_id, proposal.domainTotalRep);
}
@@ -220,17 +228,17 @@ contract FundingQueue is ColonyExtension, PatriciaTreeProofs {
require(proposal.state == ProposalState.Active, "funding-queue-proposal-not-active");
require(_id != _newPrevId, "funding-queue-cannot-insert-after-self"); // NOTE: this may be redundant
- uint256 userRep = checkReputation(_id, msg.sender, _key, _value, _branchMask, _siblings);
+ uint256 userRep = checkReputation(_id, msgSender(), _key, _value, _branchMask, _siblings);
require(_backing <= userRep, "funding-queue-insufficient-reputation");
// Update the user's reputation backing
- uint256 prevBacking = supporters[_id][msg.sender];
+ uint256 prevBacking = supporters[_id][msgSender()];
if (_backing >= prevBacking) {
proposal.totalSupport = add(proposal.totalSupport, sub(_backing, prevBacking));
} else {
proposal.totalSupport = sub(proposal.totalSupport, sub(prevBacking, _backing));
}
- supporters[_id][msg.sender] = _backing;
+ supporters[_id][msgSender()] = _backing;
// Remove the proposal from its current position, if exists
require(queue[_currPrevId] == _id, "funding-queue-bad-prev-id");
@@ -257,7 +265,7 @@ contract FundingQueue is ColonyExtension, PatriciaTreeProofs {
queue[_newPrevId] = _id; // prev proposal => this proposal
queue[_id] = nextId; // this proposal => next proposal
- emit ProposalBacked(_id, _newPrevId, msg.sender, _backing, prevBacking);
+ emit ProposalBacked(_id, _newPrevId, msgSender(), _backing, prevBacking);
}
function pingProposal(uint256 _id) public {
diff --git a/contracts/extensions/OneTxPayment.sol b/contracts/extensions/OneTxPayment.sol
index 28195389bc..ecbfcb4aa8 100644
--- a/contracts/extensions/OneTxPayment.sol
+++ b/contracts/extensions/OneTxPayment.sol
@@ -29,6 +29,15 @@ contract OneTxPayment is ColonyExtension {
ColonyDataTypes.ColonyRole constant ADMINISTRATION = ColonyDataTypes.ColonyRole.Administration;
ColonyDataTypes.ColonyRole constant FUNDING = ColonyDataTypes.ColonyRole.Funding;
+ mapping(address => uint256) metatransactionNonces;
+ function getMetatransactionNonce(address userAddress) override public view returns (uint256 nonce){
+ return metatransactionNonces[userAddress];
+ }
+
+ function incrementMetatransactionNonce(address user) override internal {
+ metatransactionNonces[user]++;
+ }
+
/// @notice Returns the identifier of the extension
function identifier() public override pure returns (bytes32) {
return keccak256("OneTxPayment");
@@ -109,8 +118,8 @@ contract OneTxPayment is ColonyExtension {
require(_workers.length == _tokens.length && _workers.length == _amounts.length, "one-tx-payment-invalid-input");
require(
- colony.hasInheritedUserRole(msg.sender, 1, FUNDING, _childSkillIndex, _domainId) &&
- colony.hasInheritedUserRole(msg.sender, _callerPermissionDomainId, ADMINISTRATION, _callerChildSkillIndex, _domainId),
+ colony.hasInheritedUserRole(msgSender(), 1, FUNDING, _childSkillIndex, _domainId) &&
+ colony.hasInheritedUserRole(msgSender(), _callerPermissionDomainId, ADMINISTRATION, _callerChildSkillIndex, _domainId),
"one-tx-payment-not-authorized"
);
@@ -125,7 +134,7 @@ contract OneTxPayment is ColonyExtension {
colony.finalizePayment(1, _childSkillIndex, paymentId);
colony.claimPayment(paymentId, _tokens[0]);
- emit OneTxPaymentMade(msg.sender, paymentId, _workers.length);
+ emit OneTxPaymentMade(msgSender(), paymentId, _workers.length);
} else {
uint256 expenditureId = colony.makeExpenditure(1, _childSkillIndex, _domainId);
@@ -157,7 +166,7 @@ contract OneTxPayment is ColonyExtension {
finalizeAndClaim(expenditureId, _workers, _tokens);
- emit OneTxPaymentMade(msg.sender, expenditureId, _workers.length);
+ emit OneTxPaymentMade(msgSender(), expenditureId, _workers.length);
}
}
@@ -190,8 +199,8 @@ contract OneTxPayment is ColonyExtension {
require(_workers.length == _tokens.length && _workers.length == _amounts.length, "one-tx-payment-invalid-input");
require(
- colony.hasInheritedUserRole(msg.sender, _callerPermissionDomainId, FUNDING, _callerChildSkillIndex, _domainId) &&
- colony.hasInheritedUserRole(msg.sender, _callerPermissionDomainId, ADMINISTRATION, _callerChildSkillIndex, _domainId),
+ colony.hasInheritedUserRole(msgSender(), _callerPermissionDomainId, FUNDING, _callerChildSkillIndex, _domainId) &&
+ colony.hasInheritedUserRole(msgSender(), _callerPermissionDomainId, ADMINISTRATION, _callerChildSkillIndex, _domainId),
"one-tx-payment-not-authorized"
);
@@ -207,7 +216,7 @@ contract OneTxPayment is ColonyExtension {
colony.finalizePayment(_permissionDomainId, _childSkillIndex, paymentId);
colony.claimPayment(paymentId, _tokens[0]);
- emit OneTxPaymentMade(msg.sender, paymentId, _workers.length);
+ emit OneTxPaymentMade(msgSender(), paymentId, _workers.length);
} else {
uint256 expenditureId = colony.makeExpenditure(_permissionDomainId, _childSkillIndex, _domainId);
@@ -240,7 +249,7 @@ contract OneTxPayment is ColonyExtension {
finalizeAndClaim(expenditureId, _workers, _tokens);
- emit OneTxPaymentMade(msg.sender, expenditureId, _workers.length);
+ emit OneTxPaymentMade(msgSender(), expenditureId, _workers.length);
}
}
diff --git a/contracts/extensions/TokenSupplier.sol b/contracts/extensions/TokenSupplier.sol
index fe2d220597..340712c267 100644
--- a/contracts/extensions/TokenSupplier.sol
+++ b/contracts/extensions/TokenSupplier.sol
@@ -39,6 +39,14 @@ contract TokenSupplier is ColonyExtension {
uint256 tokenIssuanceRate;
uint256 lastIssue;
uint256 lastRateUpdate;
+ mapping(address => uint256) metatransactionNonces;
+ function getMetatransactionNonce(address userAddress) override public view returns (uint256 nonce){
+ return metatransactionNonces[userAddress];
+ }
+
+ function incrementMetatransactionNonce(address user) override internal {
+ metatransactionNonces[user]++;
+ }
// Modifiers
@@ -168,11 +176,11 @@ contract TokenSupplier is ColonyExtension {
// Internal functions
function isRoot() internal view returns (bool) {
- return colony.hasUserRole(msg.sender, 1, ColonyDataTypes.ColonyRole.Root);
+ return colony.hasUserRole(msgSender(), 1, ColonyDataTypes.ColonyRole.Root);
}
function isRootFunding() internal view returns (bool) {
- return colony.hasUserRole(msg.sender, 1, ColonyDataTypes.ColonyRole.Funding);
+ return colony.hasUserRole(msgSender(), 1, ColonyDataTypes.ColonyRole.Funding);
}
}
diff --git a/contracts/extensions/VotingReputation.sol b/contracts/extensions/VotingReputation.sol
index 3b6021767f..7d4f335465 100644
--- a/contracts/extensions/VotingReputation.sol
+++ b/contracts/extensions/VotingReputation.sol
@@ -90,11 +90,19 @@ contract VotingReputation is ColonyExtension, PatriciaTreeProofs {
uint256 submitPeriod; // Length of time for submitting votes
uint256 revealPeriod; // Length of time for revealing votes
uint256 escalationPeriod; // Length of time for escalating after a vote
+ mapping(address => uint256) metatransactionNonces;
+ function getMetatransactionNonce(address userAddress) override public view returns (uint256 nonce){
+ return metatransactionNonces[userAddress];
+ }
+
+ function incrementMetatransactionNonce(address user) override internal {
+ metatransactionNonces[user]++;
+ }
// Modifiers
modifier onlyRoot() {
- require(colony.hasUserRole(msg.sender, 1, ColonyDataTypes.ColonyRole.Root), "voting-rep-caller-not-root");
+ require(colony.hasUserRole(msgSender(), 1, ColonyDataTypes.ColonyRole.Root), "voting-rep-caller-not-root");
_;
}
@@ -284,7 +292,7 @@ contract VotingReputation is ColonyExtension, PatriciaTreeProofs {
motion.altTarget = _altTarget;
motion.action = _action;
- emit MotionCreated(motionCount, msg.sender, _domainId);
+ emit MotionCreated(motionCount, msgSender(), _domainId);
}
/// @notice Create a motion in the root domain (DEPRECATED)
@@ -360,10 +368,10 @@ contract VotingReputation is ColonyExtension, PatriciaTreeProofs {
uint256 amount = min(_amount, sub(requiredStake, motion.stakes[_vote]));
require(amount > 0, "voting-rep-bad-amount");
- uint256 stakerTotalAmount = add(stakes[_motionId][msg.sender][_vote], amount);
+ uint256 stakerTotalAmount = add(stakes[_motionId][msgSender()][_vote], amount);
require(
- stakerTotalAmount <= getReputationFromProof(_motionId, msg.sender, _key, _value, _branchMask, _siblings),
+ stakerTotalAmount <= getReputationFromProof(_motionId, msgSender(), _key, _value, _branchMask, _siblings),
"voting-rep-insufficient-rep"
);
require(
@@ -374,7 +382,7 @@ contract VotingReputation is ColonyExtension, PatriciaTreeProofs {
// Update the stake
motion.stakes[_vote] = add(motion.stakes[_vote], amount);
- stakes[_motionId][msg.sender][_vote] = stakerTotalAmount;
+ stakes[_motionId][msgSender()][_vote] = stakerTotalAmount;
// Increment counter & extend claim delay if staking for an expenditure state change
if (
@@ -391,7 +399,7 @@ contract VotingReputation is ColonyExtension, PatriciaTreeProofs {
require(executeCall(_motionId, claimDelayAction), "voting-rep-expenditure-lock-failed");
}
- emit MotionStaked(_motionId, msg.sender, _vote, amount);
+ emit MotionStaked(_motionId, msgSender(), _vote, amount);
// Move to vote submission once both sides are fully staked
if (motion.stakes[NAY] == requiredStake && motion.stakes[YAY] == requiredStake) {
@@ -419,8 +427,8 @@ contract VotingReputation is ColonyExtension, PatriciaTreeProofs {
// Do the external bookkeeping
tokenLocking.deposit(token, 0, true); // Faux deposit to clear any locks
- colony.obligateStake(msg.sender, motion.domainId, amount);
- colony.transferStake(_permissionDomainId, _childSkillIndex, address(this), msg.sender, motion.domainId, amount, address(this));
+ colony.obligateStake(msgSender(), motion.domainId, amount);
+ colony.transferStake(_permissionDomainId, _childSkillIndex, address(this), msgSender(), motion.domainId, amount, address(this));
}
/// @notice Submit a vote secret for a motion
@@ -444,16 +452,16 @@ contract VotingReputation is ColonyExtension, PatriciaTreeProofs {
require(getMotionState(_motionId) == MotionState.Submit, "voting-rep-motion-not-open");
require(_voteSecret != bytes32(0), "voting-rep-invalid-secret");
- uint256 userRep = getReputationFromProof(_motionId, msg.sender, _key, _value, _branchMask, _siblings);
+ uint256 userRep = getReputationFromProof(_motionId, msgSender(), _key, _value, _branchMask, _siblings);
// Count reputation if first submission
- if (voteSecrets[_motionId][msg.sender] == bytes32(0)) {
+ if (voteSecrets[_motionId][msgSender()] == bytes32(0)) {
motion.repSubmitted = add(motion.repSubmitted, userRep);
}
- voteSecrets[_motionId][msg.sender] = _voteSecret;
+ voteSecrets[_motionId][msgSender()] = _voteSecret;
- emit MotionVoteSubmitted(_motionId, msg.sender);
+ emit MotionVoteSubmitted(_motionId, msgSender());
if (motion.repSubmitted >= wmul(motion.skillRep, maxVoteFraction)) {
motion.events[SUBMIT_END] = uint64(block.timestamp);
@@ -486,17 +494,17 @@ contract VotingReputation is ColonyExtension, PatriciaTreeProofs {
require(getMotionState(_motionId) == MotionState.Reveal, "voting-rep-motion-not-reveal");
require(_vote <= 1, "voting-rep-bad-vote");
- uint256 userRep = getReputationFromProof(_motionId, msg.sender, _key, _value, _branchMask, _siblings);
+ uint256 userRep = getReputationFromProof(_motionId, msgSender(), _key, _value, _branchMask, _siblings);
motion.votes[_vote] = add(motion.votes[_vote], userRep);
- bytes32 voteSecret = voteSecrets[_motionId][msg.sender];
+ bytes32 voteSecret = voteSecrets[_motionId][msgSender()];
require(voteSecret == getVoteSecret(_salt, _vote), "voting-rep-secret-no-match");
- delete voteSecrets[_motionId][msg.sender];
+ delete voteSecrets[_motionId][msgSender()];
uint256 voterReward = getVoterReward(_motionId, userRep);
motion.paidVoterComp = add(motion.paidVoterComp, voterReward);
- emit MotionVoteRevealed(_motionId, msg.sender, _vote);
+ emit MotionVoteRevealed(_motionId, msgSender(), _vote);
// See if reputation revealed matches reputation submitted
if (add(motion.votes[NAY], motion.votes[YAY]) == motion.repSubmitted) {
@@ -505,7 +513,7 @@ contract VotingReputation is ColonyExtension, PatriciaTreeProofs {
emit MotionEventSet(_motionId, REVEAL_END);
}
- tokenLocking.transfer(token, voterReward, msg.sender, true);
+ tokenLocking.transfer(token, voterReward, msgSender(), true);
}
/// @notice Escalate a motion to a higher domain
@@ -553,7 +561,7 @@ contract VotingReputation is ColonyExtension, PatriciaTreeProofs {
motion.escalated = true;
- emit MotionEscalated(_motionId, msg.sender, domainId, _newDomainId);
+ emit MotionEscalated(_motionId, msgSender(), domainId, _newDomainId);
if (motion.events[STAKE_END] <= uint64(block.timestamp)) {
emit MotionEventSet(_motionId, STAKE_END);
diff --git a/contracts/extensions/Whitelist.sol b/contracts/extensions/Whitelist.sol
index c6078fd8f2..76a9a3f166 100644
--- a/contracts/extensions/Whitelist.sol
+++ b/contracts/extensions/Whitelist.sol
@@ -37,6 +37,16 @@ contract Whitelist is ColonyExtension {
mapping (address => bool) approvals;
mapping (address => bool) signatures;
+ mapping(address => uint256) metatransactionNonces;
+
+ function getMetatransactionNonce(address userAddress) override public view returns (uint256 nonce){
+ return metatransactionNonces[userAddress];
+ }
+
+ function incrementMetatransactionNonce(address user) override internal {
+ metatransactionNonces[user]++;
+ }
+
// Modifiers
@@ -82,7 +92,7 @@ contract Whitelist is ColonyExtension {
/// @param _useApprovals Whether or not to require administrative approval
/// @param _agreementHash An agreement hash (such as an IPFS URI)
function initialise(bool _useApprovals, string memory _agreementHash) public {
- require(colony.hasUserRole(msg.sender, 1, ColonyDataTypes.ColonyRole.Root), "whitelist-unauthorised");
+ require(colony.hasUserRole(msgSender(), 1, ColonyDataTypes.ColonyRole.Root), "whitelist-unauthorised");
require(!useApprovals && bytes(agreementHash).length == 0, "whitelist-already-initialised");
require(_useApprovals || bytes(_agreementHash).length > 0, "whitelist-bad-initialisation");
@@ -97,7 +107,7 @@ contract Whitelist is ColonyExtension {
/// @param _status The whitelist status to set
function approveUsers(address[] memory _users, bool _status) public initialised notDeprecated {
require(useApprovals, "whitelist-no-approvals");
- require(colony.hasUserRole(msg.sender, 1, ColonyDataTypes.ColonyRole.Administration), "whitelist-unauthorised");
+ require(colony.hasUserRole(msgSender(), 1, ColonyDataTypes.ColonyRole.Administration), "whitelist-unauthorised");
for (uint256 i; i < _users.length; i++) {
approvals[_users[i]] = _status;
@@ -116,9 +126,9 @@ contract Whitelist is ColonyExtension {
"whitelist-bad-signature"
);
- signatures[msg.sender] = true;
+ signatures[msgSender()] = true;
- emit AgreementSigned(msg.sender);
+ emit AgreementSigned(msgSender());
}
/// @notice Get the user's overall whitelist status
diff --git a/contracts/metaTxToken/DSAuthMeta.sol b/contracts/metaTxToken/DSAuthMeta.sol
new file mode 100644
index 0000000000..e0955b5bf6
--- /dev/null
+++ b/contracts/metaTxToken/DSAuthMeta.sol
@@ -0,0 +1,61 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+import "./../common/ERC20Extended.sol";
+import "./../common/BasicMetaTransaction.sol";
+import "./../common/ERC20Extended.sol";
+import "./../../lib/dappsys/auth.sol";
+
+pragma solidity 0.7.3;
+
+abstract contract DSAuthMeta is DSAuthEvents, BasicMetaTransaction {
+ DSAuthority public authority;
+ address public owner;
+
+ constructor() {
+ owner = msgSender();
+ emit LogSetOwner(msgSender());
+ }
+
+ function setOwner(address owner_)
+ public
+ auth
+ {
+ owner = owner_;
+ emit LogSetOwner(owner);
+ }
+
+ function setAuthority(DSAuthority authority_)
+ public
+ auth
+ {
+ authority = authority_;
+ emit LogSetAuthority(address(authority));
+ }
+
+ modifier auth {
+ require(isAuthorized(msgSender(), msg.sig), "ds-auth-unauthorized");
+ _;
+ }
+
+ function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
+ if (src == address(this)) {
+ return true;
+ } else if (src == owner) {
+ return true;
+ } else if (authority == DSAuthority(0)) {
+ return false;
+ } else {
+ return authority.canCall(src, address(this), sig);
+ }
+ }
+}
\ No newline at end of file
diff --git a/contracts/metaTxToken/DSTokenBaseMeta.sol b/contracts/metaTxToken/DSTokenBaseMeta.sol
new file mode 100644
index 0000000000..34c0760b77
--- /dev/null
+++ b/contracts/metaTxToken/DSTokenBaseMeta.sol
@@ -0,0 +1,75 @@
+/// base.sol -- basic ERC20 implementation
+
+// Copyright (C) 2015, 2016, 2017 DappHub, LLC
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+// Modified to inherit BasicMetaTransaction and use msgSender() where appropriate
+
+pragma solidity 0.7.3;
+
+import "./../../lib/dappsys/erc20.sol";
+import "./../../lib/dappsys/math.sol";
+import "./../common/BasicMetaTransaction.sol";
+
+abstract contract DSTokenBaseMeta is ERC20, DSMath, BasicMetaTransaction {
+ uint256 _supply;
+ mapping (address => uint256) _balances;
+ mapping (address => mapping (address => uint256)) _approvals;
+
+ constructor(uint256 supply) {
+ _balances[msgSender()] = supply;
+ _supply = supply;
+ }
+
+ function totalSupply() public override view returns (uint) {
+ return _supply;
+ }
+ function balanceOf(address src) public override view returns (uint) {
+ return _balances[src];
+ }
+ function allowance(address src, address guy) public override view returns (uint) {
+ return _approvals[src][guy];
+ }
+
+ function transfer(address dst, uint256 wad) public override returns (bool) {
+ return transferFrom(msgSender(), dst, wad);
+ }
+
+ function transferFrom(address src, address dst, uint256 wad)
+ public override virtual
+ returns (bool)
+ {
+ if (src != msgSender()) {
+ require(_approvals[src][msgSender()] >= wad, "ds-token-insufficient-approval");
+ _approvals[src][msgSender()] = sub(_approvals[src][msgSender()], wad);
+ }
+
+ require(_balances[src] >= wad, "ds-token-insufficient-balance");
+ _balances[src] = sub(_balances[src], wad);
+ _balances[dst] = add(_balances[dst], wad);
+
+ emit Transfer(src, dst, wad);
+
+ return true;
+ }
+
+ function approve(address guy, uint256 wad) public override returns (bool) {
+ _approvals[msgSender()][guy] = wad;
+
+ emit Approval(msgSender(), guy, wad);
+
+ return true;
+ }
+}
diff --git a/contracts/metaTxToken/MetaTxToken.sol b/contracts/metaTxToken/MetaTxToken.sol
new file mode 100644
index 0000000000..0286f7ee91
--- /dev/null
+++ b/contracts/metaTxToken/MetaTxToken.sol
@@ -0,0 +1,138 @@
+/*
+ This file is part of The Colony Network.
+
+ The Colony Network is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ The Colony Network is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with The Colony Network. If not, see .
+*/
+
+pragma solidity 0.7.3;
+
+import "./DSTokenBaseMeta.sol";
+import "./DSAuthMeta.sol";
+
+contract MetaTxToken is DSTokenBaseMeta(0), DSAuthMeta {
+ uint8 public decimals;
+ string public symbol;
+ string public name;
+
+ bool public locked;
+ bytes32 public DOMAIN_SEPARATOR;
+
+ mapping(address => uint256) metatransactionNonces;
+
+ event Mint(address indexed guy, uint256 wad);
+ event Burn(address indexed guy, uint256 wad);
+
+ function getMetatransactionNonce(address _user) override public view returns (uint256 nonce){
+ return metatransactionNonces[_user];
+ }
+
+ function incrementMetatransactionNonce(address _user) override internal {
+ metatransactionNonces[_user]++;
+ }
+
+ modifier unlocked {
+ if (locked) {
+ require(isAuthorized(msgSender(), msg.sig), "colony-token-unauthorised");
+ }
+ _;
+ }
+
+ constructor(string memory _name, string memory _symbol, uint8 _decimals) {
+ name = _name;
+ symbol = _symbol;
+ decimals = _decimals;
+ locked = true;
+
+ uint256 chainId;
+ assembly {
+ chainId := chainid()
+ }
+
+ DOMAIN_SEPARATOR = keccak256(
+ abi.encode(
+ keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
+ keccak256(bytes(name)),
+ keccak256(bytes("1")),
+ chainId,
+ address(this)
+ )
+ );
+ }
+
+ function transferFrom(address src, address dst, uint256 wad) public
+ unlocked override
+ returns (bool)
+ {
+ return super.transferFrom(src, dst, wad);
+ }
+
+ function mint(uint256 wad) public auth {
+ mint(msgSender(), wad);
+ }
+
+ function burn(uint256 wad) public {
+ burn(msgSender(), wad);
+ }
+
+ function mint(address guy, uint256 wad) public auth {
+ _balances[guy] = add(_balances[guy], wad);
+ _supply = add(_supply, wad);
+
+ emit Mint(guy, wad);
+ emit Transfer(address(0x0), guy, wad);
+ }
+
+ function burn(address guy, uint256 wad) public {
+ if (guy != msgSender()) {
+ require(_approvals[guy][msgSender()] >= wad, "ds-token-insufficient-approval");
+ _approvals[guy][msgSender()] = sub(_approvals[guy][msgSender()], wad);
+ }
+
+ require(_balances[guy] >= wad, "ds-token-insufficient-balance");
+ _balances[guy] = sub(_balances[guy], wad);
+ _supply = sub(_supply, wad);
+
+ emit Burn(guy, wad);
+ }
+
+ function unlock() public
+ auth
+ {
+ locked = false;
+ }
+
+ // Pinched from https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol
+ // Which is also licenced under GPL V3
+
+ // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
+ bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
+ string constant EIP_712_PREFIX = "\x19\x01";
+
+ function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external unlocked {
+ require(deadline >= block.timestamp, "colony-token-expired-deadline");
+
+ bytes32 digest = keccak256(
+ abi.encodePacked(
+ EIP_712_PREFIX,
+ DOMAIN_SEPARATOR,
+ keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, metatransactionNonces[owner]++, deadline))
+ )
+ );
+ address recoveredAddress = ecrecover(digest, v, r, s);
+ require(recoveredAddress != address(0) && recoveredAddress == owner, "colony-token-invalid-signature");
+ _approvals[owner][spender] = value;
+
+ emit Approval(owner, spender, value);
+ }
+}
\ No newline at end of file
diff --git a/contracts/testHelpers/TestExtensions.sol b/contracts/testHelpers/TestExtensions.sol
index e61e048fd4..0503cc7dc6 100644
--- a/contracts/testHelpers/TestExtensions.sol
+++ b/contracts/testHelpers/TestExtensions.sol
@@ -37,6 +37,16 @@ abstract contract TestExtension is ColonyExtension {
function uninstall() public override auth {
selfdestruct(address(uint160(address(colony))));
}
+
+ mapping(address => uint256) metatransactionNonces;
+ function getMetatransactionNonce(address userAddress) override public view returns (uint256 nonce){
+ return metatransactionNonces[userAddress];
+ }
+
+ function incrementMetatransactionNonce(address user) override internal {
+ metatransactionNonces[user] = add(metatransactionNonces[user], 1);
+ }
+
}
diff --git a/contracts/tokenLocking/ITokenLocking.sol b/contracts/tokenLocking/ITokenLocking.sol
index 7ce262bbcb..74336b4a2f 100644
--- a/contracts/tokenLocking/ITokenLocking.sol
+++ b/contracts/tokenLocking/ITokenLocking.sol
@@ -19,9 +19,10 @@ pragma solidity >=0.7.3; // ignore-swc-103
pragma experimental "ABIEncoderV2";
import "./TokenLockingDataTypes.sol";
+import "./../common/IBasicMetaTransaction.sol";
-interface ITokenLocking is TokenLockingDataTypes {
+interface ITokenLocking is TokenLockingDataTypes, IBasicMetaTransaction {
/// @notice Set the ColonyNetwork contract address.
/// @dev ColonyNetwork is used for checking if sender is a colony created on colony network.
diff --git a/contracts/tokenLocking/TokenLocking.sol b/contracts/tokenLocking/TokenLocking.sol
index 51e3f1dd21..1a11cd1b32 100644
--- a/contracts/tokenLocking/TokenLocking.sol
+++ b/contracts/tokenLocking/TokenLocking.sol
@@ -22,14 +22,15 @@ import "./../../lib/dappsys/math.sol";
import "./../colony/IMetaColony.sol";
import "./../colonyNetwork/IColonyNetwork.sol";
import "./../common/ERC20Extended.sol";
+import "./../common/BasicMetaTransaction.sol";
import "./../reputationMiningCycle/IReputationMiningCycle.sol";
import "./../tokenLocking/TokenLockingStorage.sol";
-contract TokenLocking is TokenLockingStorage, DSMath { // ignore-swc-123
+contract TokenLocking is TokenLockingStorage, DSMath, BasicMetaTransaction { // ignore-swc-123
modifier calledByColonyOrNetwork() {
require(
- colonyNetwork == msg.sender || IColonyNetwork(colonyNetwork).isColony(msg.sender),
+ colonyNetwork == msgSender() || IColonyNetwork(colonyNetwork).isColony(msgSender()),
"colony-token-locking-sender-not-colony-or-network"
);
_;
@@ -37,15 +38,15 @@ contract TokenLocking is TokenLockingStorage, DSMath { // ignore-swc-123
modifier tokenNotLocked(address _token, bool _force) {
if (_force) {
- userLocks[_token][msg.sender].lockCount = totalLockCount[_token];
+ userLocks[_token][msgSender()].lockCount = totalLockCount[_token];
}
- require(isTokenUnlocked(_token, msg.sender), "colony-token-locking-token-locked");
+ require(isTokenUnlocked(_token, msgSender()), "colony-token-locking-token-locked");
_;
}
modifier notObligated(address _token, uint256 _amount) {
require(
- sub(userLocks[_token][msg.sender].balance, _amount) >= totalObligations[msg.sender][_token],
+ sub(userLocks[_token][msgSender()].balance, _amount) >= totalObligations[msgSender()][_token],
"colony-token-locking-excess-obligation"
);
_;
@@ -53,6 +54,14 @@ contract TokenLocking is TokenLockingStorage, DSMath { // ignore-swc-123
// Public functions
+ function getMetatransactionNonce(address userAddress) override public view returns (uint256 nonce){
+ return metatransactionNonces[userAddress];
+ }
+
+ function incrementMetatransactionNonce(address user) override internal {
+ metatransactionNonces[user] = add(metatransactionNonces[user], 1);
+ }
+
function setColonyNetwork(address _colonyNetwork) public auth {
require(_colonyNetwork != address(0x0), "colony-token-locking-network-cannot-be-zero");
@@ -67,9 +76,9 @@ contract TokenLocking is TokenLockingStorage, DSMath { // ignore-swc-123
function lockToken(address _token) public calledByColonyOrNetwork returns (uint256) {
totalLockCount[_token] += 1;
- lockers[_token][totalLockCount[_token]] = msg.sender;
+ lockers[_token][totalLockCount[_token]] = msgSender();
- emit TokenLocked(_token, msg.sender, totalLockCount[_token]);
+ emit TokenLocked(_token, msgSender(), totalLockCount[_token]);
return totalLockCount[_token];
}
@@ -77,7 +86,7 @@ contract TokenLocking is TokenLockingStorage, DSMath { // ignore-swc-123
function unlockTokenForUser(address _token, address _user, uint256 _lockId) public
calledByColonyOrNetwork
{
- require(lockers[_token][_lockId] == msg.sender, "colony-token-locking-not-locker");
+ require(lockers[_token][_lockId] == msgSender(), "colony-token-locking-not-locker");
// If we want to unlock tokens at id greater than total lock count, we are doing something wrong
require(_lockId <= totalLockCount[_token], "colony-token-invalid-lockid");
@@ -93,8 +102,8 @@ contract TokenLocking is TokenLockingStorage, DSMath { // ignore-swc-123
}
function incrementLockCounterTo(address _token, uint256 _lockId) public {
- require(_lockId <= totalLockCount[_token] && _lockId > userLocks[_token][msg.sender].lockCount, "colony-token-locking-invalid-lock-id");
- userLocks[_token][msg.sender].lockCount = _lockId;
+ require(_lockId <= totalLockCount[_token] && _lockId > userLocks[_token][msgSender()].lockCount, "colony-token-locking-invalid-lock-id");
+ userLocks[_token][msgSender()].lockCount = _lockId;
}
// Deprecated interface
@@ -103,7 +112,7 @@ contract TokenLocking is TokenLockingStorage, DSMath { // ignore-swc-123
}
function deposit(address _token, uint256 _amount, bool _force) public tokenNotLocked(_token, _force) {
- Lock storage lock = userLocks[_token][msg.sender];
+ Lock storage lock = userLocks[_token][msgSender()];
lock.balance = add(lock.balance, _amount);
// Handle the pendingBalance, if any (idempotent operation)
@@ -113,13 +122,13 @@ contract TokenLocking is TokenLockingStorage, DSMath { // ignore-swc-123
}
// Actually claim the tokens
- require(ERC20Extended(_token).transferFrom(msg.sender, address(this), _amount), "colony-token-locking-transfer-failed"); // ignore-swc-123
+ require(ERC20Extended(_token).transferFrom(msgSender(), address(this), _amount), "colony-token-locking-transfer-failed"); // ignore-swc-123
- emit UserTokenDeposited(_token, msg.sender, lock.balance);
+ emit UserTokenDeposited(_token, msgSender(), lock.balance);
}
function depositFor(address _token, uint256 _amount, address _recipient) public {
- require(ERC20Extended(_token).transferFrom(msg.sender, address(this), _amount), "colony-token-locking-transfer-failed"); // ignore-swc-123
+ require(ERC20Extended(_token).transferFrom(msgSender(), address(this), _amount), "colony-token-locking-transfer-failed"); // ignore-swc-123
makeConditionalDeposit(_token, _amount, _recipient);
@@ -130,12 +139,12 @@ contract TokenLocking is TokenLockingStorage, DSMath { // ignore-swc-123
notObligated(_token, _amount)
tokenNotLocked(_token, _force)
{
- Lock storage userLock = userLocks[_token][msg.sender];
+ Lock storage userLock = userLocks[_token][msgSender()];
userLock.balance = sub(userLock.balance, _amount);
makeConditionalDeposit(_token, _amount, _recipient);
- emit UserTokenTransferred(_token, msg.sender, _recipient, _amount);
+ emit UserTokenTransferred(_token, msgSender(), _recipient, _amount);
}
// Deprecated interface
@@ -147,23 +156,23 @@ contract TokenLocking is TokenLockingStorage, DSMath { // ignore-swc-123
notObligated(_token, _amount)
tokenNotLocked(_token, _force)
{
- Lock storage lock = userLocks[_token][msg.sender];
+ Lock storage lock = userLocks[_token][msgSender()];
lock.balance = sub(lock.balance, _amount);
- require(ERC20Extended(_token).transfer(msg.sender, _amount), "colony-token-locking-transfer-failed");
+ require(ERC20Extended(_token).transfer(msgSender(), _amount), "colony-token-locking-transfer-failed");
- emit UserTokenWithdrawn(_token, msg.sender, _amount);
+ emit UserTokenWithdrawn(_token, msgSender(), _amount);
}
function approveStake(address _user, uint256 _amount, address _token) public calledByColonyOrNetwork() {
- approvals[_user][_token][msg.sender] = add(approvals[_user][_token][msg.sender], _amount);
+ approvals[_user][_token][msgSender()] = add(approvals[_user][_token][msgSender()], _amount);
emit UserTokenApproved(_token, _user, msg.sender, _amount);
}
function obligateStake(address _user, uint256 _amount, address _token) public calledByColonyOrNetwork() {
- approvals[_user][_token][msg.sender] = sub(approvals[_user][_token][msg.sender], _amount);
- obligations[_user][_token][msg.sender] = add(obligations[_user][_token][msg.sender], _amount);
+ approvals[_user][_token][msgSender()] = sub(approvals[_user][_token][msgSender()], _amount);
+ obligations[_user][_token][msgSender()] = add(obligations[_user][_token][msgSender()], _amount);
totalObligations[_user][_token] = add(totalObligations[_user][_token], _amount);
require(userLocks[_token][_user].balance >= totalObligations[_user][_token], "colony-token-locking-insufficient-deposit");
@@ -172,14 +181,14 @@ contract TokenLocking is TokenLockingStorage, DSMath { // ignore-swc-123
}
function deobligateStake(address _user, uint256 _amount, address _token) public calledByColonyOrNetwork() {
- obligations[_user][_token][msg.sender] = sub(obligations[_user][_token][msg.sender], _amount);
+ obligations[_user][_token][msgSender()] = sub(obligations[_user][_token][msgSender()], _amount);
totalObligations[_user][_token] = sub(totalObligations[_user][_token], _amount);
emit UserTokenDeobligated(_token, _user, msg.sender, _amount);
}
function transferStake(address _user, uint256 _amount, address _token, address _recipient) public calledByColonyOrNetwork() {
- obligations[_user][_token][msg.sender] = sub(obligations[_user][_token][msg.sender], _amount);
+ obligations[_user][_token][msgSender()] = sub(obligations[_user][_token][msgSender()], _amount);
totalObligations[_user][_token] = sub(totalObligations[_user][_token], _amount);
// Transfer the the tokens
diff --git a/contracts/tokenLocking/TokenLockingStorage.sol b/contracts/tokenLocking/TokenLockingStorage.sol
index ad9780db07..616f424a90 100644
--- a/contracts/tokenLocking/TokenLockingStorage.sol
+++ b/contracts/tokenLocking/TokenLockingStorage.sol
@@ -46,4 +46,6 @@ contract TokenLockingStorage is TokenLockingDataTypes, DSAuth {
// Keep track of which colony is placing which lock ([token][lockId] => colony)
mapping (address => mapping (uint256 => address)) lockers;
+
+ mapping(address => uint256) metatransactionNonces;
}
diff --git a/docs/_Interface_IColonyNetwork.md b/docs/_Interface_IColonyNetwork.md
index e0a507aa88..d60e5204ce 100644
--- a/docs/_Interface_IColonyNetwork.md
+++ b/docs/_Interface_IColonyNetwork.md
@@ -221,6 +221,46 @@ Create the Meta Colony, same as a normal colony plus the root skill.
|_tokenAddress|address|Address of the CLNY token
+### `deployTokenAuthority`
+
+Called to deploy a token authority
+
+*Note: This is more expensive than deploying a token directly, but is able to be done via a metatransaction*
+
+**Parameters**
+
+|Name|Type|Description|
+|---|---|---|
+|_token|address|The address of the otken
+|_colony|address|The address of the colony in control of the token
+|allowedToTransfer|address[]|An array of addresses that are allowed to transfer the token even if it's locked
+
+**Return Parameters**
+
+|Name|Type|Description|
+|---|---|---|
+|address|address|
+
+### `deployTokenViaNetwork`
+
+Called to deploy a token.
+
+*Note: This is more expensive than deploying a token directly, but is able to be done via a metatransaction*
+
+**Parameters**
+
+|Name|Type|Description|
+|---|---|---|
+|_name|string|The name of the token
+|_symbol|string|The short 'ticket' symbol for the token
+|_decimals|uint8|The number of decimal places that 1 user-facing token can be divided up in to In the case of ETH, and most tokens, this is 18.
+
+**Return Parameters**
+
+|Name|Type|Description|
+|---|---|---|
+|address|address|
+
### `deprecateExtension`
Set the deprecation of an extension in a colony. Can only be called by a Colony.
diff --git a/helpers/test-data-generator.js b/helpers/test-data-generator.js
index 94c38472f9..dff585d955 100644
--- a/helpers/test-data-generator.js
+++ b/helpers/test-data-generator.js
@@ -20,7 +20,7 @@ import {
DELIVERABLE_HASH,
} from "./constants";
-import { getTokenArgs, web3GetAccounts, getChildSkillIndex } from "./test-helper";
+import { getTokenArgs, web3GetAccounts, getChildSkillIndex, web3SignTypedData } from "./test-helper";
import { executeSignedTaskChange, executeSignedRoleAssignment } from "./task-review-signing";
const IColony = artifacts.require("IColony");
@@ -28,8 +28,11 @@ const IMetaColony = artifacts.require("IMetaColony");
const ITokenLocking = artifacts.require("ITokenLocking");
const Token = artifacts.require("Token");
const TokenAuthority = artifacts.require("./TokenAuthority");
+const BasicMetaTransaction = artifacts.require("BasicMetaTransaction");
+const MultiChain = artifacts.require("MultiChain");
const EtherRouter = artifacts.require("EtherRouter");
const Resolver = artifacts.require("Resolver");
+const MetaTxToken = artifacts.require("MetaTxToken");
const IColonyNetwork = artifacts.require("IColonyNetwork");
export async function makeTask({ colonyNetwork, colony, hash = SPECIFICATION_HASH, domainId = 1, skillId = 3, dueDate = 0, manager }) {
@@ -393,3 +396,102 @@ export async function setupColony(colonyNetwork, tokenAddress) {
const colony = await IColony.at(colonyAddress);
return colony;
}
+
+export async function getMetaTransactionParameters(txData, userAddress, targetAddress) {
+ const contract = await BasicMetaTransaction.at(targetAddress);
+ const nonce = await contract.getMetatransactionNonce(userAddress);
+ // We should just be able to get the chain id via a web3 call, but until ganache sort their stuff out,
+ // we dance around the houses.
+ const multichain = await MultiChain.new();
+ const chainId = await multichain.getChainId();
+
+ // Sign data
+ const msg = web3.utils.soliditySha3(
+ { t: "uint256", v: nonce.toString() },
+ { t: "address", v: targetAddress },
+ { t: "uint256", v: chainId },
+ { t: "bytes", v: txData }
+ );
+ const sig = await web3.eth.sign(msg, userAddress);
+
+ const r = `0x${sig.substring(2, 66)}`;
+ const s = `0x${sig.substring(66, 130)}`;
+ const v = parseInt(sig.substring(130), 16) + 27;
+
+ return { r, s, v };
+}
+
+export async function getPermitParameters(owner, spender, amount, deadline, targetAddress) {
+ const contract = await MetaTxToken.at(targetAddress);
+ const nonce = await contract.getMetatransactionNonce(owner);
+ const multichain = await MultiChain.new();
+ const chainId = await multichain.getChainId();
+ const name = await contract.name();
+
+ const sigObject = {
+ types: {
+ EIP712Domain: [
+ {
+ name: "name",
+ type: "string",
+ },
+ {
+ name: "version",
+ type: "string",
+ },
+ {
+ name: "chainId",
+ type: "uint256",
+ },
+ {
+ name: "verifyingContract",
+ type: "address",
+ },
+ ],
+ Permit: [
+ {
+ name: "owner",
+ type: "address",
+ },
+ {
+ name: "spender",
+ type: "address",
+ },
+ {
+ name: "value",
+ type: "uint256",
+ },
+ {
+ name: "nonce",
+ type: "uint256",
+ },
+ {
+ name: "deadline",
+ type: "uint256",
+ },
+ ],
+ },
+ primaryType: "Permit",
+ domain: {
+ name,
+ version: "1",
+ chainId: chainId.toNumber(),
+ verifyingContract: contract.address,
+ },
+ message: {
+ owner,
+ spender,
+ value: amount,
+ nonce,
+ deadline,
+ },
+ };
+
+ const sig = await web3SignTypedData(owner, sigObject);
+
+ const r = `0x${sig.substring(2, 66)}`;
+ const s = `0x${sig.substring(66, 130)}`;
+ const v = parseInt(sig.substring(130), 16);
+
+ return { r, s, v };
+}
diff --git a/helpers/test-helper.js b/helpers/test-helper.js
index 69b4c86dae..2aa6a5048f 100644
--- a/helpers/test-helper.js
+++ b/helpers/test-helper.js
@@ -118,6 +118,22 @@ export function web3GetChainId() {
});
}
+export function web3SignTypedData(address, typedData) {
+ const packet = {
+ jsonrpc: "2.0",
+ method: "eth_signTypedData",
+ params: [address, typedData],
+ id: new Date().getTime(),
+ };
+
+ return new Promise((resolve, reject) => {
+ web3.currentProvider.send(packet, (err, res) => {
+ if (err !== null) return reject(err);
+ return resolve(res.result);
+ });
+ });
+}
+
export function web3GetRawCall(params) {
const packet = {
jsonrpc: "2.0",
diff --git a/helpers/upgradable-contracts.js b/helpers/upgradable-contracts.js
index ddf0b92c02..9e2117327b 100644
--- a/helpers/upgradable-contracts.js
+++ b/helpers/upgradable-contracts.js
@@ -78,6 +78,7 @@ export async function setupColonyVersionResolver(
colonyFunding,
colonyRoles,
contractRecovery,
+ colonyArbitraryTransaction,
resolver
) {
const deployedImplementations = {};
@@ -88,6 +89,7 @@ export async function setupColonyVersionResolver(
deployedImplementations.ColonyPayment = colonyPayment.address;
deployedImplementations.ColonyFunding = colonyFunding.address;
deployedImplementations.ContractRecovery = contractRecovery.address;
+ deployedImplementations.ColonyArbitraryTransaction = colonyArbitraryTransaction.address;
await setupEtherRouter("IMetaColony", deployedImplementations, resolver);
}
diff --git a/migrations/4_setup_colony_version_resolver.js b/migrations/4_setup_colony_version_resolver.js
index b45ce3881f..00724cab7c 100644
--- a/migrations/4_setup_colony_version_resolver.js
+++ b/migrations/4_setup_colony_version_resolver.js
@@ -9,6 +9,7 @@ const ColonyRoles = artifacts.require("./ColonyRoles");
const ColonyTask = artifacts.require("./ColonyTask");
const ColonyPayment = artifacts.require("./ColonyPayment");
const ContractRecovery = artifacts.require("./ContractRecovery");
+const ColonyArbitraryTransaction = artifacts.require("./ColonyArbitraryTransaction");
const EtherRouter = artifacts.require("./EtherRouter");
const Resolver = artifacts.require("./Resolver");
const IColonyNetwork = artifacts.require("./IColonyNetwork");
@@ -22,6 +23,7 @@ module.exports = async function (deployer) {
const colonyRoles = await ColonyRoles.new();
const colonyTask = await ColonyTask.new();
const colonyPayment = await ColonyPayment.new();
+ const colonyArbitraryTransaction = await ColonyArbitraryTransaction.new();
const contractRecovery = await ContractRecovery.deployed();
const version = await colony.version();
const resolver = await Resolver.new();
@@ -30,7 +32,17 @@ module.exports = async function (deployer) {
const colonyNetwork = await IColonyNetwork.at(etherRouterDeployed.address);
// Register the new Colony contract version with the newly setup Resolver
- await setupColonyVersionResolver(colony, colonyExpenditure, colonyTask, colonyPayment, colonyFunding, colonyRoles, contractRecovery, resolver);
+ await setupColonyVersionResolver(
+ colony,
+ colonyExpenditure,
+ colonyTask,
+ colonyPayment,
+ colonyFunding,
+ colonyRoles,
+ contractRecovery,
+ colonyArbitraryTransaction,
+ resolver
+ );
await colonyNetwork.initialise(resolver.address, version);
console.log("### Colony version", version.toString(), "set to Resolver", resolver.address);
diff --git a/migrations/8_setup_meta_colony.js b/migrations/8_setup_meta_colony.js
index a06c627cf4..cc72c535e0 100644
--- a/migrations/8_setup_meta_colony.js
+++ b/migrations/8_setup_meta_colony.js
@@ -62,6 +62,7 @@ module.exports = async function (deployer, network, accounts) {
const ColonyTask = artifacts.require("./ColonyTask");
const ColonyPayment = artifacts.require("./ColonyPayment");
const ContractRecovery = artifacts.require("./ContractRecovery");
+ const ColonyArbitraryTransaction = artifacts.require("./ColonyArbitraryTransaction");
const colony = await Colony.new();
const colonyFunding = await ColonyFunding.new();
@@ -70,15 +71,36 @@ module.exports = async function (deployer, network, accounts) {
const colonyTask = await ColonyTask.new();
const colonyPayment = await ColonyPayment.new();
const contractRecovery = await ContractRecovery.deployed();
+ const colonyArbitraryTransaction = await ColonyArbitraryTransaction.new();
const resolver3 = await Resolver.new();
- await setupColonyVersionResolver(colony, colonyExpenditure, colonyTask, colonyPayment, colonyFunding, colonyRoles, contractRecovery, resolver3);
+ await setupColonyVersionResolver(
+ colony,
+ colonyExpenditure,
+ colonyTask,
+ colonyPayment,
+ colonyFunding,
+ colonyRoles,
+ contractRecovery,
+ colonyArbitraryTransaction,
+ resolver3
+ );
const v3responder = await Version3.new();
await resolver3.register("version()", v3responder.address);
await metaColony.addNetworkColonyVersion(3, resolver3.address);
const resolver4 = await Resolver.new();
- await setupColonyVersionResolver(colony, colonyExpenditure, colonyTask, colonyPayment, colonyFunding, colonyRoles, contractRecovery, resolver4);
+ await setupColonyVersionResolver(
+ colony,
+ colonyExpenditure,
+ colonyTask,
+ colonyPayment,
+ colonyFunding,
+ colonyRoles,
+ contractRecovery,
+ colonyArbitraryTransaction,
+ resolver4
+ );
const v4responder = await Version4.new();
await resolver4.register("version()", v4responder.address);
await metaColony.addNetworkColonyVersion(4, resolver4.address);
diff --git a/scripts/check-auth.js b/scripts/check-auth.js
index 00e8c7b7ca..1da9c7b4b1 100644
--- a/scripts/check-auth.js
+++ b/scripts/check-auth.js
@@ -84,6 +84,7 @@ walkSync("./contracts/").forEach((contractName) => {
"contracts/common/EtherRouter.sol",
"contracts/common/IRecovery.sol",
"contracts/common/Resolver.sol",
+ "contracts/common/TokenAuthority.sol", // Imported from colonyToken repo
"contracts/ens/ENS.sol",
"contracts/ens/ENSRegistry.sol",
"contracts/gnosis/MultiSigWallet.sol",
@@ -106,6 +107,9 @@ walkSync("./contracts/").forEach((contractName) => {
"contracts/Migrations.sol",
"contracts/Token.sol", // Imported from colonyToken repo
"contracts/TokenAuthority.sol", // Imported from colonyToken repo
+ "contracts/metaTxToken/MetaTxToken.sol",
+ "contracts/metaTxToken/DSAuthMeta.sol",
+ "contracts/metaTxToken/DSTokenBaseMeta.sol",
].indexOf(contractName) > -1
) {
return;
diff --git a/scripts/check-recovery.js b/scripts/check-recovery.js
index b9131c20a3..87f8e9547a 100644
--- a/scripts/check-recovery.js
+++ b/scripts/check-recovery.js
@@ -30,6 +30,8 @@ walkSync("./contracts/").forEach((contractName) => {
"contracts/colonyNetwork/ColonyNetworkAuthority.sol",
"contracts/colonyNetwork/ColonyNetworkStorage.sol",
"contracts/colonyNetwork/IColonyNetwork.sol",
+ "contracts/common/BasicMetaTransaction.sol",
+ "contracts/common/IBasicMetaTransaction.sol",
"contracts/common/CommonAuthority.sol",
"contracts/common/DomainRoles.sol",
"contracts/common/ERC20Extended.sol",
@@ -37,6 +39,7 @@ walkSync("./contracts/").forEach((contractName) => {
"contracts/common/IEtherRouter.sol",
"contracts/common/IRecovery.sol",
"contracts/common/Resolver.sol",
+ "contracts/common/TokenAuthority.sol", // Imported from colonyToken repo
"contracts/ens/ENS.sol",
"contracts/ens/ENSRegistry.sol",
"contracts/extensions/CoinMachine.sol",
@@ -75,6 +78,9 @@ walkSync("./contracts/").forEach((contractName) => {
"contracts/Migrations.sol",
"contracts/Token.sol", // Imported from colonyToken repo
"contracts/TokenAuthority.sol", // Imported from colonyToken repo
+ "contracts/metaTxToken/MetaTxToken.sol",
+ "contracts/metaTxToken/DSAuthMeta.sol",
+ "contracts/metaTxToken/DSTokenBaseMeta.sol",
].indexOf(contractName) > -1
) {
return;
diff --git a/scripts/check-storage.js b/scripts/check-storage.js
index ec3f1658ee..a7abe077c0 100644
--- a/scripts/check-storage.js
+++ b/scripts/check-storage.js
@@ -23,6 +23,7 @@ walkSync("./contracts/").forEach((contractName) => {
"contracts/common/DomainRoles.sol",
"contracts/common/EtherRouter.sol",
"contracts/common/Resolver.sol",
+ "contracts/common/TokenAuthority.sol", // Imported from colonyToken repo
"contracts/ens/ENSRegistry.sol", // Not directly used by any colony contracts
"contracts/extensions/CoinMachine.sol",
"contracts/extensions/EvaluatedExpenditure.sol",
@@ -36,10 +37,14 @@ walkSync("./contracts/").forEach((contractName) => {
"contracts/patriciaTree/PatriciaTreeBase.sol", // Only used by mining clients
"contracts/reputationMiningCycle/ReputationMiningCycleStorage.sol",
"contracts/testHelpers/ToggleableToken.sol",
+ "contracts/testHelpers/TestExtensions.sol",
"contracts/tokenLocking/TokenLockingStorage.sol",
"contracts/Migrations.sol",
"contracts/Token.sol", // Imported from colonyToken repo
"contracts/TokenAuthority.sol", // Imported from colonyToken repo
+ "contracts/metaTxToken/MetaTxToken.sol",
+ "contracts/metaTxToken/DSAuthMeta.sol",
+ "contracts/metaTxToken/DSTokenBaseMeta.sol",
].indexOf(contractName) > -1
) {
return;
diff --git a/scripts/generate-test-contracts.sh b/scripts/generate-test-contracts.sh
index e0e8e06777..a47a74352c 100644
--- a/scripts/generate-test-contracts.sh
+++ b/scripts/generate-test-contracts.sh
@@ -22,7 +22,7 @@ sed -i.bak "s/address resolver;/address resolver;function isUpdated() public pur
sed -i.bak "s/contract Colony/contract UpdatedColony/g" ./contracts/colony/UpdatedColony.sol
sed -i.bak "s/ColonyStorage/UpdatedColonyStorage/g" ./contracts/colony/UpdatedColony.sol
sed -i.bak "s/function version() public pure returns (uint256 colonyVersion) { return ${version}/function version() public pure returns (uint256 colonyVersion) { return ${updated_version}/g" ./contracts/colony/UpdatedColony.sol
-sed -i.bak "s/contract UpdatedColony is UpdatedColonyStorage, PatriciaTreeProofs, MultiChain {/contract UpdatedColony is UpdatedColonyStorage, PatriciaTreeProofs, MultiChain {function isUpdated() external pure returns(bool) {return true;}/g" ./contracts/colony/UpdatedColony.sol
+sed -i.bak "s/contract UpdatedColony is BasicMetaTransaction, UpdatedColonyStorage, PatriciaTreeProofs {/contract UpdatedColony is BasicMetaTransaction, UpdatedColonyStorage, PatriciaTreeProofs {function isUpdated() external pure returns(bool) {return true;}/g" ./contracts/colony/UpdatedColony.sol
# Modify UpdatedColonyDataTypes contract
sed -i.bak "s/ColonyDataTypes/UpdatedColonyDataTypes/g" ./contracts/colony/UpdatedColonyDataTypes.sol
sed -i.bak "s/mapping (uint8 => mapping (address => uint256)) payouts;/mapping (uint8 => mapping (address => uint256)) payouts; uint256 x;/g" ./contracts/colony/UpdatedColonyDataTypes.sol
@@ -32,7 +32,7 @@ sed -i.bak "s/ColonyDataTypes/UpdatedColonyDataTypes/g" ./contracts/colony/Updat
# Modify IUpdatedColony contract
sed -i.bak "s/interface IColony/interface IUpdatedColony/g" ./contracts/colony/IUpdatedColony.sol
sed -i.bak "s/ColonyDataTypes/UpdatedColonyDataTypes/g" ./contracts/colony/IUpdatedColony.sol
-sed -i.bak "s/interface IUpdatedColony is UpdatedColonyDataTypes, IRecovery {/interface IUpdatedColony is UpdatedColonyDataTypes, IRecovery {function isUpdated() external pure returns(bool);/g" ./contracts/colony/IUpdatedColony.sol
+sed -i.bak "s/interface IUpdatedColony is UpdatedColonyDataTypes, IRecovery, IBasicMetaTransaction {/interface IUpdatedColony is UpdatedColonyDataTypes, IRecovery, IBasicMetaTransaction {function isUpdated() external pure returns(bool);/g" ./contracts/colony/IUpdatedColony.sol
# Modify UpdatedReputationMiningCycle contract
sed -i.bak "s/contract ReputationMiningCycle/contract UpdatedReputationMiningCycle/g" ./contracts/reputationMiningCycle/UpdatedReputationMiningCycle.sol
sed -i.bak "s| is ReputationMiningCycleCommon {| is ReputationMiningCycleCommon {\nfunction isUpdated() public pure returns(bool) {return true;}|g" ./contracts/reputationMiningCycle/UpdatedReputationMiningCycle.sol
diff --git a/test-smoke/colony-storage-consistent.js b/test-smoke/colony-storage-consistent.js
index c49a6b1239..8a83d7a29e 100644
--- a/test-smoke/colony-storage-consistent.js
+++ b/test-smoke/colony-storage-consistent.js
@@ -154,11 +154,11 @@ contract("Contract Storage", (accounts) => {
console.log("miningCycleStateHash:", miningCycleAccount.stateRoot.toString("hex"));
console.log("tokenLockingStateHash:", tokenLockingAccount.stateRoot.toString("hex"));
- expect(colonyNetworkAccount.stateRoot.toString("hex")).to.equal("fc9c8702501fa11dce57db3f679b0909a2b252cf71a73aac17ef4a159271ff56");
- expect(colonyAccount.stateRoot.toString("hex")).to.equal("d8e895aa214956a2543325209231d4502c6c241027df357ce9bcf065215c4632");
- expect(metaColonyAccount.stateRoot.toString("hex")).to.equal("78294685a492256887e3159e26071ba06d62883c72ccb26fd323a883eefc30fd");
- expect(miningCycleAccount.stateRoot.toString("hex")).to.equal("e105190bcd647989da1579ac209ae54ed63e08224fbb2469bad9f596773fe558");
- expect(tokenLockingAccount.stateRoot.toString("hex")).to.equal("8bab6ab2024de44a08765ee14ec3d6bdc0fa5ae2a5ee221c9f928345a3710658");
+ expect(colonyNetworkAccount.stateRoot.toString("hex")).to.equal("cbe7c27231f4c94f1fdff92a685599c6ada69af16a0f32ab3a72e85334a4a2ca");
+ expect(colonyAccount.stateRoot.toString("hex")).to.equal("b198bd282eac14f7dc4d71817c4184462c6db7ae11a8f7662edecd4afbba6234");
+ expect(metaColonyAccount.stateRoot.toString("hex")).to.equal("c91229b9b01734f45e65feea0561ed90bee1365c953ceb187e87e80e9a96ef86");
+ expect(miningCycleAccount.stateRoot.toString("hex")).to.equal("f1ae4c855f083837446bc31b3355ca0291daa363dd9afb655de51829a46fc635");
+ expect(tokenLockingAccount.stateRoot.toString("hex")).to.equal("860aa632a7a9a21119b0e27c30d0c3f4da5916eb5263c7870d4dda3eb7162e32");
});
});
});
diff --git a/test-upgrade/colony-upgrade.js b/test-upgrade/colony-upgrade.js
index 96ca4b4962..eee98a3d4c 100644
--- a/test-upgrade/colony-upgrade.js
+++ b/test-upgrade/colony-upgrade.js
@@ -14,6 +14,7 @@ const ColonyPayment = artifacts.require("ColonyPayment");
const ColonyFunding = artifacts.require("ColonyFunding");
const ColonyRoles = artifacts.require("ColonyRoles");
const ContractRecovery = artifacts.require("ContractRecovery");
+const ColonyArbitraryTransaction = artifacts.require("ColonyArbitraryTransaction");
const UpdatedColony = artifacts.require("UpdatedColony");
const IUpdatedColony = artifacts.require("IUpdatedColony");
@@ -44,6 +45,7 @@ contract("Colony contract upgrade", (accounts) => {
const colonyFunding = await ColonyFunding.new();
const colonyRoles = await ColonyRoles.new();
const contractRecovery = await ContractRecovery.new();
+ const colonyArbitraryTransaction = await ColonyArbitraryTransaction.new();
dueDate = await currentBlockTime();
await makeTask({ colony, dueDate });
@@ -61,6 +63,7 @@ contract("Colony contract upgrade", (accounts) => {
colonyFunding,
colonyRoles,
contractRecovery,
+ colonyArbitraryTransaction,
resolver
);
diff --git a/test/contracts-network/colony-network-auction.js b/test/contracts-network/colony-network-auction.js
index 588574b936..fc0d8c1321 100644
--- a/test/contracts-network/colony-network-auction.js
+++ b/test/contracts-network/colony-network-auction.js
@@ -13,8 +13,15 @@ import {
getBlockTime,
getColonyEditable,
} from "../../helpers/test-helper";
+
import { WAD, SECONDS_PER_DAY } from "../../helpers/constants";
-import { setupColonyNetwork, setupMetaColonyWithLockedCLNYToken, unlockCLNYToken, giveUserCLNYTokens } from "../../helpers/test-data-generator";
+import {
+ getMetaTransactionParameters,
+ setupColonyNetwork,
+ setupMetaColonyWithLockedCLNYToken,
+ unlockCLNYToken,
+ giveUserCLNYTokens,
+} from "../../helpers/test-data-generator";
const { expect } = chai;
chai.use(bnChai(web3.utils.BN));
@@ -261,6 +268,22 @@ contract("Colony Network Auction", (accounts) => {
expect(bidCount).to.eq.BN(1);
});
+ it("can bid via metatransaction", async () => {
+ await giveUserCLNYTokens(colonyNetwork, BIDDER_1, WAD);
+ await clnyToken.approve(tokenAuction.address, WAD, { from: BIDDER_1 });
+ // await tokenAuction.bid(WAD, { from: BIDDER_1 });
+ const txData = await tokenAuction.contract.methods.bid(WAD.toString()).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, BIDDER_1, tokenAuction.address);
+
+ await tokenAuction.executeMetaTransaction(BIDDER_1, txData, r, s, v, { from: accounts[0] });
+
+ const bid = await tokenAuction.bids(BIDDER_1);
+ expect(bid).to.eq.BN(WAD);
+ const bidCount = await tokenAuction.bidCount();
+ expect(bidCount).to.eq.BN(1);
+ });
+
it("bid tokens are locked", async () => {
await giveUserCLNYTokens(colonyNetwork, BIDDER_1, WAD);
await clnyToken.approve(tokenAuction.address, WAD, { from: BIDDER_1 });
diff --git a/test/contracts-network/colony-network-recovery.js b/test/contracts-network/colony-network-recovery.js
index 6c95e1cd57..30d5e0107e 100644
--- a/test/contracts-network/colony-network-recovery.js
+++ b/test/contracts-network/colony-network-recovery.js
@@ -17,11 +17,18 @@ import {
web3GetStorageAt,
getActiveRepCycle,
advanceMiningCycleNoContest,
+ getTokenArgs,
} from "../../helpers/test-helper";
-import { setupFinalizedTask, giveUserCLNYTokensAndStake, fundColonyWithTokens, setupRandomColony } from "../../helpers/test-data-generator";
+import {
+ setupFinalizedTask,
+ giveUserCLNYTokensAndStake,
+ fundColonyWithTokens,
+ setupRandomColony,
+ getMetaTransactionParameters,
+} from "../../helpers/test-data-generator";
import ReputationMinerTestWrapper from "../../packages/reputation-miner/test/ReputationMinerTestWrapper";
import { setupEtherRouter } from "../../helpers/upgradable-contracts";
-import { DEFAULT_STAKE, MINING_CYCLE_DURATION } from "../../helpers/constants";
+import { DEFAULT_STAKE, MINING_CYCLE_DURATION, CURR_VERSION } from "../../helpers/constants";
const { expect } = chai;
chai.use(bnChai(web3.utils.BN));
@@ -195,6 +202,45 @@ contract("Colony Network Recovery", (accounts) => {
await colonyNetwork.exitRecoveryMode();
});
+ it("should not allow editing of a protected variable in a mapping", async () => {
+ // First, set a variable in a protected mapping. Currently, the only way we do that
+ // is via a metatransaction
+ const tokenArgs = getTokenArgs();
+ const token = await Token.new(...tokenArgs);
+
+ let txData = await colonyNetwork.contract.methods["createColony(address,uint256,string)"](token.address, CURR_VERSION, "").encodeABI();
+
+ txData = await colonyNetwork.contract.methods.createColony(token.address, CURR_VERSION, "someColonyName").encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, accounts[1], colonyNetwork.address);
+
+ await colonyNetwork.executeMetaTransaction(accounts[1], txData, r, s, v, { from: accounts[0] });
+
+ // Put network in to recovery mode
+ await colonyNetwork.enterRecoveryMode();
+ // work out the storage slot
+ // Metatransaction nonce mapping is storage slot 41
+ // So this user has their nonce stored at
+ const user0MetatransactionNonceSlot = await web3.utils.soliditySha3(
+ { type: "bytes32", value: ethers.utils.hexZeroPad(accounts[1], 32) },
+ { type: "uint256", value: "41" }
+ );
+
+ // Try and edit that slot
+ await checkErrorRevert(
+ colonyNetwork.setStorageSlotRecovery(user0MetatransactionNonceSlot, "0x00000000000000000000000000000000000000000000000000000000000000ff"),
+ "colony-protected-variable"
+ );
+
+ // Try and edit the protection
+ const user0MetatransactionNonceProtectionSlot = web3.utils.soliditySha3("RECOVERY_PROTECTED", user0MetatransactionNonceSlot);
+ await checkErrorRevert(colonyNetwork.setStorageSlotRecovery(user0MetatransactionNonceProtectionSlot, "0x00"), "colony-protected-variable");
+
+ // Leave recovery mode
+ await colonyNetwork.approveExitRecovery();
+ await colonyNetwork.exitRecoveryMode();
+ });
+
it("should not be able to call recovery functions while not in recovery mode", async () => {
await checkErrorRevert(colonyNetwork.approveExitRecovery(), "colony-not-in-recovery-mode");
await checkErrorRevert(colonyNetwork.exitRecoveryMode(), "colony-not-in-recovery-mode");
diff --git a/test/contracts-network/colony-network.js b/test/contracts-network/colony-network.js
index de30f4e0c2..9c2798d4af 100755
--- a/test/contracts-network/colony-network.js
+++ b/test/contracts-network/colony-network.js
@@ -2,6 +2,7 @@
import chai from "chai";
import bnChai from "bn-chai";
import { ethers } from "ethers";
+import { soliditySha3 } from "web3-utils";
import {
getTokenArgs,
@@ -13,7 +14,12 @@ import {
getColonyEditable,
} from "../../helpers/test-helper";
import { CURR_VERSION, GLOBAL_SKILL_ID, MIN_STAKE, IPFS_HASH } from "../../helpers/constants";
-import { setupColonyNetwork, setupMetaColonyWithLockedCLNYToken, setupRandomColony } from "../../helpers/test-data-generator";
+import {
+ setupColonyNetwork,
+ setupMetaColonyWithLockedCLNYToken,
+ setupRandomColony,
+ getMetaTransactionParameters,
+} from "../../helpers/test-data-generator";
import { setupENSRegistrar } from "../../helpers/upgradable-contracts";
const namehash = require("eth-ens-namehash");
@@ -25,7 +31,10 @@ const ENSRegistry = artifacts.require("ENSRegistry");
const EtherRouter = artifacts.require("EtherRouter");
const Resolver = artifacts.require("Resolver");
const IColonyNetwork = artifacts.require("IColonyNetwork");
+const IColony = artifacts.require("IColony");
const Token = artifacts.require("Token");
+const TokenAuthority = artifacts.require("TokenAuthority");
+const MetaTxToken = artifacts.require("MetaTxToken");
const FunctionsNotAvailableOnColony = artifacts.require("FunctionsNotAvailableOnColony");
contract("Colony Network", (accounts) => {
@@ -335,6 +344,38 @@ contract("Colony Network", (accounts) => {
});
});
+ describe("when users create tokens", () => {
+ it("should allow users to create new tokens", async () => {
+ const tx = await colonyNetwork.deployTokenViaNetwork("TEST", "TST", 18);
+ await expectEvent(tx, "TokenDeployed", []);
+ });
+
+ it("should have the user as the owner of the token", async () => {
+ const tx = await colonyNetwork.deployTokenViaNetwork("TEST", "TST", 18);
+ const address = tx.logs[0].args.tokenAddress;
+ const token = await MetaTxToken.at(address);
+ const owner = await token.owner();
+ expect(owner).to.equal(accounts[0]);
+ });
+
+ it("should allow users to create new token authorities", async () => {
+ let tx = await colonyNetwork.deployTokenViaNetwork("TEST", "TST", 18);
+ const { tokenAddress } = tx.logs[0].args;
+ tx = await colonyNetwork.deployTokenAuthority(tokenAddress, metaColony.address, [accounts[0]]);
+ await expectEvent(tx, "TokenAuthorityDeployed", []);
+ const authorityAddress = tx.logs[0].args.tokenAuthorityAddress;
+ const authority = await TokenAuthority.at(authorityAddress);
+
+ const transferSig = soliditySha3("transfer(address,uint256)").slice(0, 10);
+ let ableToTransfer = await authority.canCall(metaColony.address, tokenAddress, transferSig);
+ expect(ableToTransfer).to.be.true;
+ ableToTransfer = await authority.canCall(accounts[0], tokenAddress, transferSig);
+ expect(ableToTransfer).to.be.true;
+ ableToTransfer = await authority.canCall(accounts[1], tokenAddress, transferSig);
+ expect(ableToTransfer).to.be.false;
+ });
+ });
+
describe("when getting existing colonies", () => {
it("should allow users to get the address of a colony by its index", async () => {
const token = await Token.new(...TOKEN_ARGS);
@@ -640,4 +681,62 @@ contract("Colony Network", (accounts) => {
await checkErrorRevert(colony.updateColonyOrbitDB("anotherstring", { from: accounts[0] }), "colony-colony-not-labeled");
});
});
+
+ describe("when executing metatransactions", () => {
+ beforeEach(async () => {
+ const ensRegistry = await ENSRegistry.new();
+ await setupENSRegistrar(colonyNetwork, ensRegistry, accounts[0]);
+ });
+
+ it("should allow colony creation via metatransactions, with ENS registration afterwards", async () => {
+ const tokenArgs = getTokenArgs();
+ const token = await Token.new(...tokenArgs);
+
+ let txData = await colonyNetwork.contract.methods.createColony(token.address, CURR_VERSION, "").encodeABI();
+
+ let { r, s, v } = await getMetaTransactionParameters(txData, accounts[1], colonyNetwork.address);
+
+ let tx = await colonyNetwork.executeMetaTransaction(accounts[1], txData, r, s, v, { from: accounts[0] });
+
+ const colonyCount = await colonyNetwork.getColonyCount();
+ const colonyAddress = await colonyNetwork.getColony(colonyCount);
+ await expectEvent(tx, "ColonyAdded", [colonyCount, colonyAddress, token.address]);
+
+ const colony = await IColony.at(colonyAddress);
+ txData = await colony.contract.methods.registerColonyLabel("someColonyName", "").encodeABI();
+
+ ({ r, s, v } = await getMetaTransactionParameters(txData, accounts[1], colony.address));
+
+ tx = await colony.executeMetaTransaction(accounts[1], txData, r, s, v, { from: accounts[0] });
+ });
+
+ it("should allow colony creation via metatransactions, with ENS registration at the time", async () => {
+ const tokenArgs = getTokenArgs();
+ const token = await Token.new(...tokenArgs);
+
+ let txData = await colonyNetwork.contract.methods["createColony(address,uint256,string)"](token.address, CURR_VERSION, "").encodeABI();
+
+ txData = await colonyNetwork.contract.methods.createColony(token.address, CURR_VERSION, "someColonyName").encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, accounts[1], colonyNetwork.address);
+
+ const tx = await colonyNetwork.executeMetaTransaction(accounts[1], txData, r, s, v, { from: accounts[0] });
+
+ const colonyCount = await colonyNetwork.getColonyCount();
+ const colonyAddress = await colonyNetwork.getColony(colonyCount);
+ await expectEvent(tx, "ColonyAdded", [colonyCount, colonyAddress, token.address]);
+ });
+
+ it("should have the user as the owner of a token deployed through ColonyNetwork via metatransaction", async () => {
+ const txData = await colonyNetwork.contract.methods["deployTokenViaNetwork(string,string,uint8)"]("Test token", "TST", 18).encodeABI();
+ const { r, s, v } = await getMetaTransactionParameters(txData, accounts[1], colonyNetwork.address);
+
+ const tx = await colonyNetwork.executeMetaTransaction(accounts[1], txData, r, s, v, { from: accounts[0] });
+
+ const address = tx.logs[0].args.tokenAddress;
+ const token = await MetaTxToken.at(address);
+ const owner = await token.owner();
+ expect(owner).to.equal(accounts[1]);
+ });
+ });
});
diff --git a/test/contracts-network/colony-recovery.js b/test/contracts-network/colony-recovery.js
index a376ebe678..be8fe5e873 100644
--- a/test/contracts-network/colony-recovery.js
+++ b/test/contracts-network/colony-recovery.js
@@ -6,7 +6,7 @@ import { ethers } from "ethers";
import { UINT256_MAX, SPECIFICATION_HASH } from "../../helpers/constants";
import { web3GetStorageAt, checkErrorRevert, expectEvent } from "../../helpers/test-helper";
-import { setupRandomColony } from "../../helpers/test-data-generator";
+import { setupRandomColony, getMetaTransactionParameters } from "../../helpers/test-data-generator";
const EtherRouter = artifacts.require("EtherRouter");
const IColonyNetwork = artifacts.require("IColonyNetwork");
@@ -195,7 +195,7 @@ contract("Colony Recovery", (accounts) => {
expect(unprotected).to.eq.BN(`0xdeadbeef${"0".repeat(56)}`);
});
- it("should not allow editing of protected variables", async () => {
+ it("should not allow editing of protected variables in a protected slot", async () => {
await colony.enterRecoveryMode();
await checkErrorRevert(colony.setStorageSlotRecovery(0, "0xdeadbeef"), "colony-common-protected-variable");
await checkErrorRevert(colony.setStorageSlotRecovery(1, "0xdeadbeef"), "colony-common-protected-variable");
@@ -204,6 +204,36 @@ contract("Colony Recovery", (accounts) => {
await checkErrorRevert(colony.setStorageSlotRecovery(6, "0xdeadbeef"), "colony-protected-variable");
});
+ it("should not allow editing of a protected variable in a mapping", async () => {
+ // First, set a variable in a protected mapping. Currently, the only way we do that
+ // is via a metatransaction
+ const txData = await colony.contract.methods.mintTokens(100).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, accounts[0], colony.address);
+
+ await colony.executeMetaTransaction(accounts[0], txData, r, s, v, { from: accounts[1] });
+
+ // Put colony in to recovery mode
+ await colony.enterRecoveryMode();
+ // work out the storage slot
+ // Metatransaction nonce mapping is storage slot 35
+ // So this user has their nonce stored at
+ const user0MetatransactionNonceSlot = await web3.utils.soliditySha3(
+ { type: "bytes32", value: ethers.utils.hexZeroPad(accounts[0], 32) },
+ { type: "uint256", value: "35" }
+ );
+
+ // Try and edit that slot
+ await checkErrorRevert(
+ colony.setStorageSlotRecovery(user0MetatransactionNonceSlot, "0x00000000000000000000000000000000000000000000000000000000000000ff"),
+ "colony-protected-variable"
+ );
+
+ // Try and edit the protection
+ const user0MetatransactionNonceProtectionSlot = web3.utils.soliditySha3("RECOVERY_PROTECTED", user0MetatransactionNonceSlot);
+ await checkErrorRevert(colony.setStorageSlotRecovery(user0MetatransactionNonceProtectionSlot, "0x00"), "colony-protected-variable");
+ });
+
it("should allow upgrade to be called on a colony in and out of recovery mode", async () => {
// Note that we can't upgrade, because we don't have a new version. But this test is still valid, because we're getting the
// 'version must be newer' error, not a `colony-not-in-recovery-mode` or `colony-in-recovery-mode` error.
diff --git a/test/contracts-network/colony.js b/test/contracts-network/colony.js
index 510723aa43..62eef25780 100755
--- a/test/contracts-network/colony.js
+++ b/test/contracts-network/colony.js
@@ -16,7 +16,7 @@ import {
WAD,
} from "../../helpers/constants";
import { getTokenArgs, web3GetBalance, checkErrorRevert, expectNoEvent, expectAllEvents, expectEvent } from "../../helpers/test-helper";
-import { makeTask, setupRandomColony } from "../../helpers/test-data-generator";
+import { makeTask, setupRandomColony, getMetaTransactionParameters } from "../../helpers/test-data-generator";
const { expect } = chai;
chai.use(bnChai(web3.utils.BN));
@@ -327,6 +327,18 @@ contract("Colony", (accounts) => {
});
});
+ describe("when executing metatransactions", () => {
+ it("should allow a metatransaction to occur", async () => {
+ const txData = await colony.contract.methods.mintTokens(100).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER0, colony.address);
+
+ const tx = await colony.executeMetaTransaction(USER0, txData, r, s, v, { from: USER1 });
+
+ await expectEvent(tx, "TokensMinted(address,address,uint256)", [USER0, colony.address, 100]);
+ });
+ });
+
describe("when burning tokens", async () => {
beforeEach(async () => {
await colony.mintTokens(WAD);
diff --git a/test/contracts-network/metatx-token.js b/test/contracts-network/metatx-token.js
new file mode 100644
index 0000000000..49b941f552
--- /dev/null
+++ b/test/contracts-network/metatx-token.js
@@ -0,0 +1,708 @@
+/* globals artifacts */
+import chai from "chai";
+import bnChai from "bn-chai";
+import { ethers } from "ethers";
+import { web3GetBalance, checkErrorRevert, expectEvent } from "../../helpers/test-helper";
+import { getMetaTransactionParameters, getPermitParameters, setupColony } from "../../helpers/test-data-generator";
+
+const { expect } = chai;
+chai.use(bnChai(web3.utils.BN));
+
+const EtherRouter = artifacts.require("EtherRouter");
+const IColonyNetwork = artifacts.require("IColonyNetwork");
+const MetaTxToken = artifacts.require("MetaTxToken");
+
+const ADDRESS_ZERO = ethers.constants.AddressZero;
+
+contract("MetaTxToken", (accounts) => {
+ const USER0 = accounts[0];
+ const USER1 = accounts[1];
+ const USER2 = accounts[2];
+
+ let colony;
+ let metaTxToken;
+ let colonyNetwork;
+
+ before(async () => {
+ const etherRouter = await EtherRouter.deployed();
+ colonyNetwork = await IColonyNetwork.at(etherRouter.address);
+ });
+
+ beforeEach(async () => {
+ metaTxToken = await MetaTxToken.new("Test", "TEST", 18);
+ colony = await setupColony(colonyNetwork, metaTxToken.address);
+ await colony.setRewardInverse(100);
+ });
+
+ describe("when using the contract directly", () => {
+ describe("when working with MetaTxToken, should behave like normal token", () => {
+ beforeEach("mint 1500000 tokens", async () => {
+ await metaTxToken.unlock({ from: USER0 });
+ await metaTxToken.mint(USER0, 1500000, { from: USER0 });
+ });
+
+ it("should be able to get total supply", async () => {
+ const total = await metaTxToken.totalSupply();
+ expect(total).to.eq.BN(1500000);
+ });
+
+ it("should be able to get token balance", async () => {
+ const balance = await metaTxToken.balanceOf(USER0);
+ expect(balance).to.eq.BN(1500000);
+ });
+
+ it("should be able to get allowance for address", async () => {
+ await metaTxToken.approve(USER1, 200000, { from: USER0 });
+ const allowance = await metaTxToken.allowance(USER0, USER1);
+ expect(allowance).to.eq.BN(200000);
+ });
+
+ it("should be able to transfer tokens from own address", async () => {
+ const success = await metaTxToken.transfer.call(USER1, 300000, { from: USER0 });
+ expect(success).to.be.true;
+ await expectEvent(metaTxToken.transfer(USER1, 300000, { from: USER0 }), "Transfer", [USER0, USER1, 300000]);
+ const balanceAccount1 = await metaTxToken.balanceOf(USER0);
+ expect(balanceAccount1).to.eq.BN(1200000);
+ const balanceAccount2 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount2).to.eq.BN(300000);
+ });
+
+ it("should NOT be able to transfer more tokens than they have", async () => {
+ await checkErrorRevert(metaTxToken.transfer(USER1, 1500001, { from: USER0 }), "ds-token-insufficient-balance");
+ const balanceAccount2 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount2).to.be.zero;
+ });
+
+ it("should be able to transfer pre-approved tokens from address different than own", async () => {
+ await metaTxToken.approve(USER1, 300000, { from: USER0 });
+ const success = await metaTxToken.transferFrom.call(USER0, USER1, 300000, { from: USER1 });
+ expect(success).to.be.true;
+
+ await expectEvent(metaTxToken.transferFrom(USER0, USER1, 300000, { from: USER1 }), "Transfer", [USER0, USER1, 300000]);
+ const balanceAccount1 = await metaTxToken.balanceOf(USER0);
+ expect(balanceAccount1).to.eq.BN(1200000);
+ const balanceAccount2 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount2).to.eq.BN(300000);
+ const allowance = await metaTxToken.allowance(USER0, USER1);
+ expect(allowance).to.be.zero;
+ });
+
+ it("should NOT be able to transfer tokens from another address if NOT pre-approved", async () => {
+ await checkErrorRevert(metaTxToken.transferFrom(USER0, USER1, 300000, { from: USER1 }), "ds-token-insufficient-approval");
+ const balanceAccount2 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount2).to.be.zero;
+ });
+
+ it("should NOT be able to transfer from another address more tokens than pre-approved", async () => {
+ await metaTxToken.approve(USER1, 300000);
+ await checkErrorRevert(metaTxToken.transferFrom(USER0, USER1, 300001, { from: USER1 }), "ds-token-insufficient-approval");
+
+ const balanceAccount2 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount2).to.be.zero;
+ });
+
+ it("should NOT be able to transfer from another address more tokens than the source balance", async () => {
+ await metaTxToken.approve(USER1, 300000, { from: USER0 });
+ await metaTxToken.transfer(USER2, 1500000, { from: USER0 });
+
+ await checkErrorRevert(metaTxToken.transferFrom(USER0, USER1, 300000, { from: USER1 }), "ds-token-insufficient-balance");
+ const balanceAccount2 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount2).to.be.zero;
+ });
+
+ it("should be able to approve token transfer for other accounts", async () => {
+ const success = await metaTxToken.approve.call(USER1, 200000, { from: USER0 });
+ expect(success).to.be.true;
+
+ await expectEvent(metaTxToken.approve(USER1, 200000, { from: USER0 }), "Approval", [USER0, USER1, 200000]);
+ const allowance = await metaTxToken.allowance(USER0, USER1);
+ expect(allowance).to.eq.BN(200000);
+ });
+ });
+
+ describe("when working with ERC20 functions and token is locked", () => {
+ beforeEach(async () => {
+ await metaTxToken.mint(USER0, 1500000, { from: USER0 });
+ await metaTxToken.transfer(USER1, 1500000, { from: USER0 });
+ });
+
+ it("shouldn't be able to transfer tokens from own address", async () => {
+ await checkErrorRevert(metaTxToken.transfer(USER2, 300000, { from: USER1 }), "colony-token-unauthorised");
+
+ const balanceAccount1 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount1).to.eq.BN(1500000);
+ const balanceAccount2 = await metaTxToken.balanceOf(USER2);
+ expect(balanceAccount2).to.be.zero;
+ });
+
+ it("shouldn't be able to transfer pre-approved tokens", async () => {
+ await metaTxToken.approve(USER2, 300000, { from: USER1 });
+ await checkErrorRevert(metaTxToken.transferFrom(USER1, USER2, 300000, { from: USER2 }), "colony-token-unauthorised");
+
+ const balanceAccount1 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount1).to.eq.BN(1500000);
+ const balanceAccount2 = await metaTxToken.balanceOf(USER2);
+ expect(balanceAccount2).to.be.zero;
+ const allowance = await metaTxToken.allowance(USER1, USER2);
+ expect(allowance).to.eq.BN(300000);
+ });
+ });
+
+ describe("when working with additional functions", () => {
+ it("should be able to get the token decimals", async () => {
+ const decimals = await metaTxToken.decimals();
+ expect(decimals).to.eq.BN(18);
+ });
+
+ it("should be able to get the token symbol", async () => {
+ const symbol = await metaTxToken.symbol();
+ expect(symbol).to.equal("TEST");
+ });
+
+ it("should be able to get the token name", async () => {
+ const name = await metaTxToken.name();
+ expect(name).to.equal("Test");
+ });
+
+ it("should be able to mint new tokens, when called by the Token owner", async () => {
+ await metaTxToken.mint(USER0, 1500000, { from: USER0 });
+
+ let totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.eq.BN(1500000);
+
+ let balance = await metaTxToken.balanceOf(USER0);
+ expect(balance).to.eq.BN(1500000);
+
+ // Mint some more tokens
+ await expectEvent(metaTxToken.mint(USER0, 1, { from: USER0 }), "Mint", [USER0, 1]);
+ totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.eq.BN(1500001);
+
+ balance = await metaTxToken.balanceOf(USER0);
+ expect(balance).to.eq.BN(1500001);
+ });
+
+ it("should be able to mint new tokens directly to sender, when called by the Token owner", async () => {
+ // How truffle supports function overloads apparently
+ await metaTxToken.methods["mint(uint256)"](1500000, { from: USER0 });
+
+ const totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.eq.BN(1500000);
+
+ const balance = await metaTxToken.balanceOf(USER0);
+ expect(balance).to.eq.BN(1500000);
+ });
+
+ it("should emit a Mint event when minting tokens", async () => {
+ await expectEvent(metaTxToken.mint(USER0, 1, { from: USER0 }), "Mint", [USER0, 1]);
+ await expectEvent(metaTxToken.methods["mint(uint256)"](1, { from: USER0 }), "Mint", [USER0, 1]);
+ });
+
+ it("should emit a Transfer event when minting tokens", async () => {
+ await expectEvent(metaTxToken.mint(USER0, 1, { from: USER0 }), "Transfer", [ADDRESS_ZERO, USER0, 1]);
+ await expectEvent(metaTxToken.methods["mint(uint256)"](1, { from: USER0 }), "Transfer", [ADDRESS_ZERO, USER0, 1]);
+ });
+
+ it("should NOT be able to mint new tokens, when called by anyone NOT the Token owner", async () => {
+ await checkErrorRevert(metaTxToken.mint(USER0, 1500000, { from: USER2 }), "ds-auth-unauthorized");
+ const totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.be.zero;
+ });
+
+ it("should be able to burn others' tokens when they have approved them to do so", async () => {
+ await metaTxToken.mint(USER1, 1500000, { from: USER0 });
+ await metaTxToken.methods["approve(address,uint256)"](USER2, 500000, { from: USER1 });
+ await metaTxToken.methods["burn(address,uint256)"](USER1, 500000, { from: USER2 });
+
+ const totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.eq.BN(1000000);
+
+ const balance = await metaTxToken.balanceOf(USER1);
+ expect(balance).to.eq.BN(1000000);
+ });
+
+ it("should NOT be able to burn others' tokens when the approved amount is less", async () => {
+ await metaTxToken.mint(USER1, 1500000, { from: USER0 });
+ await metaTxToken.methods["approve(address,uint256)"](USER2, 500000, { from: USER1 });
+ await checkErrorRevert(metaTxToken.methods["burn(address,uint256)"](USER1, 500001, { from: USER2 }), "ds-token-insufficient-approval");
+
+ const totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.eq.BN(1500000);
+
+ const balance = await metaTxToken.balanceOf(USER1);
+ expect(balance).to.eq.BN(1500000);
+ });
+
+ it("should be able to burn own tokens", async () => {
+ // How truffle supports function overloads apparently
+ await metaTxToken.mint(USER1, 1500000, { from: USER0 });
+ await metaTxToken.methods["burn(address,uint256)"](USER1, 500000, { from: USER1 });
+
+ let totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.eq.BN(1000000);
+
+ let balance = await metaTxToken.balanceOf(USER1);
+ expect(balance).to.eq.BN(1000000);
+
+ await metaTxToken.methods["burn(uint256)"](1000000, { from: USER1 });
+ totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.be.zero;
+
+ balance = await metaTxToken.balanceOf(USER1);
+ expect(balance).to.be.zero;
+ });
+
+ it("should NOT be able to burn tokens if there's insufficient balance", async () => {
+ await metaTxToken.mint(USER1, 5, { from: USER0 });
+ await checkErrorRevert(metaTxToken.burn(6, { from: USER1 }), "ds-token-insufficient-balance");
+
+ const balance = await metaTxToken.balanceOf(USER1);
+ expect(balance).to.eq.BN(5);
+ });
+
+ it("should emit a Burn event when burning tokens", async () => {
+ await metaTxToken.methods["mint(uint256)"](1, { from: USER0 });
+ await expectEvent(metaTxToken.burn(1, { from: USER0 }), "Burn", [USER0, 1]);
+ });
+
+ it("should be able to unlock token by owner", async () => {
+ // Note: due to an apparent bug, we cannot call a parameterless function with transaction params, e.g. { from: senderAccount }
+ // So change the owner to coinbase so we are able to call it without params
+ await metaTxToken.setOwner(USER0, { from: USER0 });
+ await metaTxToken.unlock();
+ await metaTxToken.setAuthority(USER0);
+
+ const locked = await metaTxToken.locked();
+ expect(locked).to.be.false;
+
+ const tokenAuthorityLocal = await metaTxToken.authority();
+ expect(tokenAuthorityLocal).to.equal(USER0);
+ });
+
+ it("shouldn't be able to unlock token by non-owner", async () => {
+ await checkErrorRevert(metaTxToken.unlock({ from: USER2 }), "ds-auth-unauthorized");
+ await checkErrorRevert(metaTxToken.setAuthority(USER0, { from: USER2 }), "ds-auth-unauthorized");
+
+ const locked = await metaTxToken.locked();
+ expect(locked).to.be.true;
+ });
+ });
+
+ describe("when working with ether transfers", () => {
+ it("should NOT accept eth", async () => {
+ await checkErrorRevert(metaTxToken.send(2));
+ const tokenBalance = await web3GetBalance(metaTxToken.address);
+ expect(tokenBalance).to.be.zero;
+ });
+ });
+ });
+
+ describe("when using the contract through metatransactions", () => {
+ describe("when working with MetaTxToken, should behave like normal token", () => {
+ beforeEach("mint 1500000 tokens", async () => {
+ await metaTxToken.unlock({ from: USER0 });
+ await metaTxToken.mint(USER0, 1500000, { from: USER0 });
+ });
+
+ it("should be able to transfer tokens from own address", async () => {
+ const txData = await metaTxToken.contract.methods.transfer(USER1, 300000).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER0, metaTxToken.address);
+
+ const tx = await metaTxToken.executeMetaTransaction(USER0, txData, r, s, v, { from: USER1 });
+
+ await expectEvent(tx, "Transfer", [USER0, USER1, 300000]);
+ const balanceAccount1 = await metaTxToken.balanceOf(USER0);
+ expect(balanceAccount1).to.eq.BN(1200000);
+ const balanceAccount2 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount2).to.eq.BN(300000);
+ });
+
+ it("metatransaction should not be able to be replayed", async () => {
+ const txData = await metaTxToken.contract.methods.transfer(USER1, 300000).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER0, metaTxToken.address);
+
+ await metaTxToken.executeMetaTransaction(USER0, txData, r, s, v, { from: USER1 });
+
+ await checkErrorRevert(
+ metaTxToken.executeMetaTransaction(USER0, txData, r, s, v, { from: USER1 }),
+ "metatransaction-signer-signature-mismatch"
+ );
+ });
+
+ it("should NOT be able to transfer more tokens than they have", async () => {
+ const txData = await metaTxToken.contract.methods.transfer(USER1, 1500001).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER0, metaTxToken.address);
+
+ await checkErrorRevert(
+ metaTxToken.executeMetaTransaction(USER0, txData, r, s, v, { from: USER1 }),
+ "colony-metatx-function-call-unsuccessful"
+ );
+
+ const balanceAccount2 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount2).to.be.zero;
+ });
+
+ it("should be able to transfer pre-approved tokens from address different than own", async () => {
+ await metaTxToken.approve(USER1, 300000, { from: USER0 });
+ const txData = await metaTxToken.contract.methods.transferFrom(USER0, USER1, 300000).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER1, metaTxToken.address);
+
+ await expectEvent(metaTxToken.executeMetaTransaction(USER1, txData, r, s, v, { from: USER2 }), "Transfer", [USER0, USER1, 300000]);
+ const balanceAccount1 = await metaTxToken.balanceOf(USER0);
+ expect(balanceAccount1).to.eq.BN(1200000);
+ const balanceAccount2 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount2).to.eq.BN(300000);
+ const allowance = await metaTxToken.allowance(USER0, USER1);
+ expect(allowance).to.be.zero;
+ });
+
+ it("should NOT be able to transfer tokens from another address if NOT pre-approved", async () => {
+ const txData = await metaTxToken.contract.methods.transferFrom(USER0, USER1, 300000).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER1, metaTxToken.address);
+
+ await checkErrorRevert(
+ metaTxToken.executeMetaTransaction(USER1, txData, r, s, v, { from: USER2 }),
+ "colony-metatx-function-call-unsuccessful"
+ );
+ const balanceAccount2 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount2).to.be.zero;
+ });
+
+ it("should NOT be able to transfer from another address more tokens than pre-approved", async () => {
+ await metaTxToken.approve(USER1, 300000);
+ const txData = await metaTxToken.contract.methods.transferFrom(USER0, USER1, 300001).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER1, metaTxToken.address);
+
+ await checkErrorRevert(
+ metaTxToken.executeMetaTransaction(USER1, txData, r, s, v, { from: USER2 }),
+ "colony-metatx-function-call-unsuccessful"
+ );
+
+ const balanceAccount2 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount2).to.be.zero;
+ });
+
+ it("should NOT be able to transfer from another address more tokens than the source balance", async () => {
+ await metaTxToken.approve(USER1, 300000, { from: USER0 });
+ await metaTxToken.transfer(USER2, 1500000, { from: USER0 });
+ const txData = await metaTxToken.contract.methods.transferFrom(USER0, USER1, 300001).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER1, metaTxToken.address);
+
+ await checkErrorRevert(
+ metaTxToken.executeMetaTransaction(USER1, txData, r, s, v, { from: USER2 }),
+ "colony-metatx-function-call-unsuccessful"
+ );
+ const balanceAccount2 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount2).to.be.zero;
+ });
+
+ it("should be able to approve token transfer for other accounts", async () => {
+ const txData = await metaTxToken.contract.methods.approve(USER1, 200000).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER0, metaTxToken.address);
+
+ await expectEvent(metaTxToken.executeMetaTransaction(USER0, txData, r, s, v, { from: USER2 }), "Approval", [USER0, USER1, 200000]);
+ const allowance = await metaTxToken.allowance(USER0, USER1);
+ expect(allowance).to.eq.BN(200000);
+ });
+ });
+
+ describe("when working with ERC20 functions and token is locked", () => {
+ beforeEach(async () => {
+ await metaTxToken.mint(USER0, 1500000, { from: USER0 });
+ await metaTxToken.transfer(USER1, 1500000, { from: USER0 });
+ });
+
+ it("shouldn't be able to transfer tokens from own address", async () => {
+ const txData = await metaTxToken.contract.methods.transfer(USER2, 300000).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER1, metaTxToken.address);
+
+ await checkErrorRevert(
+ metaTxToken.executeMetaTransaction(USER1, txData, r, s, v, { from: USER2 }),
+ "colony-metatx-function-call-unsuccessful"
+ );
+
+ const balanceAccount1 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount1).to.eq.BN(1500000);
+ const balanceAccount2 = await metaTxToken.balanceOf(USER2);
+ expect(balanceAccount2).to.be.zero;
+ });
+
+ it("shouldn't be able to transfer pre-approved tokens", async () => {
+ await metaTxToken.approve(USER2, 300000, { from: USER1 });
+
+ const txData = await metaTxToken.contract.methods.transferFrom(USER1, USER2, 300000).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER2, metaTxToken.address);
+
+ await checkErrorRevert(
+ metaTxToken.executeMetaTransaction(USER2, txData, r, s, v, { from: USER0 }),
+ "colony-metatx-function-call-unsuccessful"
+ );
+
+ const balanceAccount1 = await metaTxToken.balanceOf(USER1);
+ expect(balanceAccount1).to.eq.BN(1500000);
+ const balanceAccount2 = await metaTxToken.balanceOf(USER2);
+ expect(balanceAccount2).to.be.zero;
+ const allowance = await metaTxToken.allowance(USER1, USER2);
+ expect(allowance).to.eq.BN(300000);
+ });
+ });
+
+ describe("when working with additional functions", () => {
+ it("should be able to mint new tokens, when called by the Token owner", async () => {
+ let txData = await metaTxToken.contract.methods.mint(USER0, 1500000).encodeABI();
+
+ let { r, s, v } = await getMetaTransactionParameters(txData, USER0, metaTxToken.address);
+
+ await metaTxToken.executeMetaTransaction(USER0, txData, r, s, v, { from: USER1 });
+
+ let totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.eq.BN(1500000);
+
+ let balance = await metaTxToken.balanceOf(USER0);
+ expect(balance).to.eq.BN(1500000);
+
+ // Mint some more tokens
+ txData = await metaTxToken.contract.methods.mint(USER0, 1).encodeABI();
+
+ ({ r, s, v } = await getMetaTransactionParameters(txData, USER0, metaTxToken.address));
+
+ const tx = metaTxToken.executeMetaTransaction(USER0, txData, r, s, v, { from: USER1 });
+
+ await expectEvent(tx, "Mint", [USER0, 1]);
+ totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.eq.BN(1500001);
+
+ balance = await metaTxToken.balanceOf(USER0);
+ expect(balance).to.eq.BN(1500001);
+ });
+
+ it("should be able to mint new tokens directly to sender, when called by the Token owner", async () => {
+ const txData = await metaTxToken.contract.methods["mint(uint256)"](1500000).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER0, metaTxToken.address);
+
+ await metaTxToken.executeMetaTransaction(USER0, txData, r, s, v, { from: USER1 });
+
+ const totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.eq.BN(1500000);
+
+ const balance = await metaTxToken.balanceOf(USER0);
+ expect(balance).to.eq.BN(1500000);
+ });
+
+ it("should NOT be able to mint new tokens, when called by anyone NOT the Token owner", async () => {
+ const txData = await metaTxToken.contract.methods.mint(USER0).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER1, metaTxToken.address);
+
+ await checkErrorRevert(
+ metaTxToken.executeMetaTransaction(USER1, txData, r, s, v, { from: USER0 }),
+ "colony-metatx-function-call-unsuccessful"
+ );
+
+ const totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.be.zero;
+ });
+
+ it("should be able to burn others' tokens when they have approved them to do so", async () => {
+ await metaTxToken.mint(USER1, 1500000, { from: USER0 });
+ await metaTxToken.methods["approve(address,uint256)"](USER2, 500000, { from: USER1 });
+ // await metaTxToken.approve(USER2, 500000, { FROM: USER1 });
+ // await metaTxToken.burn(USER1, 500000, { from: USER2 });
+
+ const txData = await metaTxToken.contract.methods["burn(address,uint256)"](USER1, 500000).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER2, metaTxToken.address);
+
+ await metaTxToken.executeMetaTransaction(USER2, txData, r, s, v, { from: USER0 });
+
+ const totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.eq.BN(1000000);
+
+ const balance = await metaTxToken.balanceOf(USER1);
+ expect(balance).to.eq.BN(1000000);
+ });
+
+ it("should NOT be able to burn others' tokens when the approved amount is less", async () => {
+ await metaTxToken.mint(USER1, 1500000, { from: USER0 });
+ await metaTxToken.methods["approve(address,uint256)"](USER2, 500000, { from: USER1 });
+
+ const txData = await metaTxToken.contract.methods["burn(address,uint256)"](USER2, 500001).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER2, metaTxToken.address);
+
+ await checkErrorRevert(
+ metaTxToken.executeMetaTransaction(USER2, txData, r, s, v, { from: USER0 }),
+ "colony-metatx-function-call-unsuccessful"
+ );
+
+ const totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.eq.BN(1500000);
+
+ const balance = await metaTxToken.balanceOf(USER1);
+ expect(balance).to.eq.BN(1500000);
+ });
+
+ it("should be able to burn own tokens", async () => {
+ // How truffle supports function overloads apparently
+ await metaTxToken.mint(USER1, 1500000, { from: USER0 });
+
+ const txData = await metaTxToken.contract.methods["burn(address,uint256)"](USER1, 500000).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER1, metaTxToken.address);
+
+ await metaTxToken.executeMetaTransaction(USER1, txData, r, s, v, { from: USER0 });
+
+ let totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.eq.BN(1000000);
+
+ let balance = await metaTxToken.balanceOf(USER1);
+ expect(balance).to.eq.BN(1000000);
+
+ await metaTxToken.methods["burn(uint256)"](1000000, { from: USER1 });
+ totalSupply = await metaTxToken.totalSupply();
+ expect(totalSupply).to.be.zero;
+
+ balance = await metaTxToken.balanceOf(USER1);
+ expect(balance).to.be.zero;
+ });
+
+ it("should NOT be able to burn tokens if there's insufficient balance", async () => {
+ await metaTxToken.mint(USER1, 5, { from: USER0 });
+
+ const txData = await metaTxToken.contract.methods.burn(6).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER1, metaTxToken.address);
+
+ await checkErrorRevert(
+ metaTxToken.executeMetaTransaction(USER1, txData, r, s, v, { from: USER0 }),
+ "colony-metatx-function-call-unsuccessful"
+ );
+
+ const balance = await metaTxToken.balanceOf(USER1);
+ expect(balance).to.eq.BN(5);
+ });
+
+ it("should be able to unlock token by owner", async () => {
+ // Note: due to an apparent bug, we cannot call a parameterless function with transaction params, e.g. { from: senderAccount }
+ // So change the owner to coinbase so we are able to call it without params
+ await metaTxToken.setOwner(USER0, { from: USER0 });
+
+ let txData = await metaTxToken.contract.methods.unlock().encodeABI();
+
+ let { r, s, v } = await getMetaTransactionParameters(txData, USER0, metaTxToken.address);
+
+ await metaTxToken.executeMetaTransaction(USER0, txData, r, s, v, { from: USER1 });
+
+ txData = await metaTxToken.contract.methods.setAuthority(USER0).encodeABI();
+ ({ r, s, v } = await getMetaTransactionParameters(txData, USER0, metaTxToken.address));
+
+ await metaTxToken.executeMetaTransaction(USER0, txData, r, s, v, { from: USER1 });
+
+ const locked = await metaTxToken.locked();
+ expect(locked).to.be.false;
+
+ const tokenAuthorityLocal = await metaTxToken.authority();
+ expect(tokenAuthorityLocal).to.equal(USER0);
+ });
+
+ it("shouldn't be able to unlock token by non-owner", async () => {
+ let txData = await metaTxToken.contract.methods.unlock().encodeABI();
+
+ let { r, s, v } = await getMetaTransactionParameters(txData, USER2, metaTxToken.address);
+
+ await checkErrorRevert(
+ metaTxToken.executeMetaTransaction(USER2, txData, r, s, v, { from: USER0 }),
+ "colony-metatx-function-call-unsuccessful"
+ );
+
+ txData = await metaTxToken.contract.methods.setAuthority(USER0).encodeABI();
+
+ ({ r, s, v } = await getMetaTransactionParameters(txData, USER1, metaTxToken.address));
+
+ await checkErrorRevert(
+ metaTxToken.executeMetaTransaction(USER1, txData, r, s, v, { from: USER0 }),
+ "colony-metatx-function-call-unsuccessful"
+ );
+
+ const locked = await metaTxToken.locked();
+ expect(locked).to.be.true;
+ const tokenAuthorityLocal = await metaTxToken.authority();
+ expect(tokenAuthorityLocal).to.equal(ADDRESS_ZERO);
+ });
+ });
+ });
+
+ describe("when using the permit functionality", () => {
+ it("permit should work", async () => {
+ await metaTxToken.unlock();
+
+ let allowance = await metaTxToken.allowance(USER0, USER1);
+ expect(allowance).to.eq.BN(0);
+
+ const { r, s, v } = await getPermitParameters(USER0, USER1, 100, 1000000000000, metaTxToken.address);
+
+ const tx = await metaTxToken.permit(USER0, USER1, 100, 1000000000000, v, r, s, { from: USER2 });
+
+ await expectEvent(tx, "Approval", [USER0, USER1, 100]);
+
+ allowance = await metaTxToken.allowance(USER0, USER1);
+ expect(allowance).to.eq.BN(100);
+ });
+
+ it("permit with deadline in the past doesn't work", async () => {
+ await metaTxToken.unlock();
+
+ const { r, s, v } = await getPermitParameters(USER0, USER1, 100, 1, metaTxToken.address);
+
+ await checkErrorRevert(metaTxToken.permit(USER0, USER1, 100, 1, v, r, s, { from: USER2 }), "colony-token-expired-deadline");
+
+ const allowance = await metaTxToken.allowance(USER0, USER1);
+ expect(allowance).to.eq.BN(0);
+ });
+
+ it("permit does not allow a tx to be replayed", async () => {
+ await metaTxToken.unlock();
+
+ let allowance = await metaTxToken.allowance(USER0, USER1);
+ expect(allowance).to.eq.BN(0);
+
+ const { r, s, v } = await getPermitParameters(USER0, USER1, 100, 1000000000000, metaTxToken.address);
+
+ await metaTxToken.permit(USER0, USER1, 100, 1000000000000, v, r, s, { from: USER2 });
+
+ await metaTxToken.approve(USER1, 300000, { from: USER0 });
+
+ await checkErrorRevert(metaTxToken.permit(USER0, USER1, 100, 1000000000000, v, r, s, { from: USER2 }), "colony-token-invalid-signature");
+
+ allowance = await metaTxToken.allowance(USER0, USER1);
+ expect(allowance).to.eq.BN(300000);
+ });
+
+ it("permit expects a valid signature", async () => {
+ await metaTxToken.unlock();
+
+ let allowance = await metaTxToken.allowance(USER0, USER1);
+ expect(allowance).to.eq.BN(0);
+
+ const { r, s } = await getPermitParameters(USER0, USER1, 100, 1000000000000, metaTxToken.address);
+
+ const v = 100;
+
+ await checkErrorRevert(metaTxToken.permit(USER0, USER1, 100, 1000000000000, v, r, s, { from: USER2 }), "colony-token-invalid-signature");
+
+ allowance = await metaTxToken.allowance(USER0, USER1);
+ expect(allowance).to.eq.BN(0);
+ });
+ });
+});
diff --git a/test/contracts-network/token-locking.js b/test/contracts-network/token-locking.js
index 85bd76415a..2ada308eb8 100644
--- a/test/contracts-network/token-locking.js
+++ b/test/contracts-network/token-locking.js
@@ -7,7 +7,13 @@ import { soliditySha3 } from "web3-utils";
import TruffleLoader from "../../packages/reputation-miner/TruffleLoader";
import { getTokenArgs, checkErrorRevert, makeReputationKey, advanceMiningCycleNoContest, expectEvent } from "../../helpers/test-helper";
-import { giveUserCLNYTokensAndStake, setupColony, setupRandomColony, fundColonyWithTokens } from "../../helpers/test-data-generator";
+import {
+ giveUserCLNYTokensAndStake,
+ setupColony,
+ setupRandomColony,
+ fundColonyWithTokens,
+ getMetaTransactionParameters,
+} from "../../helpers/test-data-generator";
import { UINT256_MAX, DEFAULT_STAKE } from "../../helpers/constants";
import { setupEtherRouter } from "../../helpers/upgradable-contracts";
@@ -176,6 +182,21 @@ contract("Token Locking", (addresses) => {
const balance = await token.balanceOf(otherUserAddress);
expect(balance).to.eq.BN(usersTokens);
});
+
+ it("should allow deposits to be made via metatransaction", async () => {
+ await token.approve(tokenLocking.address, usersTokens, { from: userAddress });
+
+ const txData = await tokenLocking.contract.methods["deposit(address,uint256,bool)"](token.address, usersTokens, true).encodeABI();
+ const { r, s, v } = await getMetaTransactionParameters(txData, userAddress, tokenLocking.address);
+
+ await tokenLocking.executeMetaTransaction(userAddress, txData, r, s, v, { from: otherUserAddress });
+
+ const info = await tokenLocking.getUserLock(token.address, userAddress);
+ expect(info.balance).to.eq.BN(usersTokens);
+
+ const tokenLockingContractBalance = await token.balanceOf(tokenLocking.address);
+ expect(tokenLockingContractBalance).to.eq.BN(usersTokens);
+ });
});
describe("when withdrawing tokens", async () => {
diff --git a/test/extensions/coin-machine.js b/test/extensions/coin-machine.js
index 2b569dd177..3cd89d764c 100644
--- a/test/extensions/coin-machine.js
+++ b/test/extensions/coin-machine.js
@@ -26,6 +26,7 @@ import {
setupRandomColony,
setupColony,
setupMetaColonyWithLockedCLNYToken,
+ getMetaTransactionParameters,
} from "../../helpers/test-data-generator";
const { expect } = chai;
@@ -234,6 +235,26 @@ contract("Coin Machine", (accounts) => {
await checkErrorRevert(coinMachine.buyTokens(WAD, { from: USER0 }), "ds-token-insufficient-balance");
});
+ it("can buy tokens via metatransaction", async () => {
+ await purchaseToken.mint(USER0, WAD, { from: USER0 });
+ await purchaseToken.approve(coinMachine.address, WAD, { from: USER0 });
+
+ const txData = await coinMachine.contract.methods.buyTokens(WAD.toString()).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER0, coinMachine.address);
+
+ await coinMachine.executeMetaTransaction(USER0, txData, r, s, v, { from: USER1 });
+
+ const userBalance = await token.balanceOf(USER0);
+ expect(userBalance).to.eq.BN(WAD);
+ const colonyBalance = await purchaseToken.balanceOf(colony.address);
+ expect(colonyBalance).to.eq.BN(WAD);
+
+ // But not with insufficient funds
+ await purchaseToken.approve(coinMachine.address, WAD, { from: USER0 });
+ await checkErrorRevert(coinMachine.buyTokens(WAD, { from: USER0 }), "ds-token-insufficient-balance");
+ });
+
it("responds to getter functions correctly while running", async () => {
await purchaseToken.mint(USER0, WAD, { from: USER0 });
await purchaseToken.approve(coinMachine.address, WAD, { from: USER0 });
diff --git a/test/extensions/funding-queue.js b/test/extensions/funding-queue.js
index 86ef0211ec..b61b27fe4c 100644
--- a/test/extensions/funding-queue.js
+++ b/test/extensions/funding-queue.js
@@ -24,6 +24,7 @@ import {
setupRandomColony,
giveUserCLNYTokensAndStake,
setupMetaColonyWithLockedCLNYToken,
+ getMetaTransactionParameters,
} from "../../helpers/test-data-generator";
import { setupEtherRouter } from "../../helpers/upgradable-contracts";
@@ -228,6 +229,23 @@ contract("Funding Queues", (accounts) => {
expect(proposal.state).to.eq.BN(STATE_INACTIVE);
});
+ it("can create a basic proposal via metatransaction", async () => {
+ await fundingQueue.createProposal(1, UINT256_MAX, 0, 1, 2, WAD, token.address, { from: USER0 });
+ const txData = await fundingQueue.contract.methods
+ .createProposal(1, UINT256_MAX.toString(), 0, 1, 2, WAD.toString(), token.address)
+ .encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER0, fundingQueue.address);
+
+ await fundingQueue.executeMetaTransaction(USER0, txData, r, s, v, { from: USER1 });
+
+ const proposalId = await fundingQueue.getProposalCount();
+
+ const proposal = await fundingQueue.getProposal(proposalId);
+ expect(proposal.domainId).to.eq.BN(1);
+ expect(proposal.state).to.eq.BN(STATE_INACTIVE);
+ });
+
it("cannot create a basic proposal if deprecated", async () => {
let deprecated = await fundingQueue.getDeprecated();
expect(deprecated).to.equal(false);
diff --git a/test/extensions/one-tx-payment.js b/test/extensions/one-tx-payment.js
index 65459f1d43..0c3f5a4b5f 100644
--- a/test/extensions/one-tx-payment.js
+++ b/test/extensions/one-tx-payment.js
@@ -7,7 +7,13 @@ import { soliditySha3 } from "web3-utils";
import { UINT256_MAX, WAD, INITIAL_FUNDING, GLOBAL_SKILL_ID, FUNDING_ROLE, ADMINISTRATION_ROLE } from "../../helpers/constants";
import { checkErrorRevert, web3GetCode, rolesToBytes32, expectEvent } from "../../helpers/test-helper";
-import { setupColonyNetwork, setupMetaColonyWithLockedCLNYToken, setupRandomColony, fundColonyWithTokens } from "../../helpers/test-data-generator";
+import {
+ setupColonyNetwork,
+ setupMetaColonyWithLockedCLNYToken,
+ setupRandomColony,
+ fundColonyWithTokens,
+ getMetaTransactionParameters,
+} from "../../helpers/test-data-generator";
import { setupEtherRouter } from "../../helpers/upgradable-contracts";
const { expect } = chai;
@@ -112,6 +118,33 @@ contract("One transaction payments", (accounts) => {
await expectEvent(tx, "OneTxPaymentMade", [accounts[0], 1, 1]);
});
+ it("should allow a single-transaction payment of tokens to occur via metatransaction", async () => {
+ const balanceBefore = await token.balanceOf(USER1);
+ expect(balanceBefore).to.be.zero;
+
+ const txData = await oneTxPayment.contract.methods
+ .makePaymentFundedFromDomain(
+ 1,
+ UINT256_MAX.toString(),
+ 1,
+ UINT256_MAX.toString(),
+ [USER1],
+ [token.address],
+ [10],
+ 1,
+ GLOBAL_SKILL_ID.toString()
+ )
+ .encodeABI();
+ const { r, s, v } = await getMetaTransactionParameters(txData, accounts[0], oneTxPayment.address);
+
+ const tx = await oneTxPayment.executeMetaTransaction(accounts[0], txData, r, s, v, { from: USER2 });
+
+ const balanceAfter = await token.balanceOf(USER1);
+ expect(balanceAfter).to.eq.BN(9);
+
+ await expectEvent(tx, "OneTxPaymentMade", [accounts[0], 1, 1]);
+ });
+
it("should allow a single-transaction payment of ETH to occur", async () => {
const balanceBefore = await web3.eth.getBalance(USER1);
await colony.send(10); // NB 10 wei, not ten ether!
diff --git a/test/extensions/token-supplier.js b/test/extensions/token-supplier.js
index e21de7518c..f19fc5e9bc 100644
--- a/test/extensions/token-supplier.js
+++ b/test/extensions/token-supplier.js
@@ -8,7 +8,12 @@ import { soliditySha3 } from "web3-utils";
import { UINT256_MAX, WAD, SECONDS_PER_DAY } from "../../helpers/constants";
import { checkErrorRevert, currentBlockTime, makeTxAtTimestamp, getBlockTime, forwardTime } from "../../helpers/test-helper";
-import { setupColonyNetwork, setupRandomColony, setupMetaColonyWithLockedCLNYToken } from "../../helpers/test-data-generator";
+import {
+ setupColonyNetwork,
+ setupRandomColony,
+ setupMetaColonyWithLockedCLNYToken,
+ getMetaTransactionParameters,
+} from "../../helpers/test-data-generator";
import { setupEtherRouter } from "../../helpers/upgradable-contracts";
const { expect } = chai;
@@ -218,6 +223,24 @@ contract("Token Supplier", (accounts) => {
expect(tokenSupply).to.eq.BN(WAD);
});
+ it("can claim tokenIssuanceRate tokens per day via metatransaction", async () => {
+ const balancePre = await token.balanceOf(colony.address);
+
+ let time = await currentBlockTime();
+ time = new BN(time).addn(SECONDS_PER_DAY);
+
+ const txData = await tokenSupplier.contract.methods.issueTokens().encodeABI();
+ const { r, s, v } = await getMetaTransactionParameters(txData, accounts[0], tokenSupplier.address);
+
+ await makeTxAtTimestamp(tokenSupplier.executeMetaTransaction, [accounts[0], txData, r, s, v], time.toNumber(), this);
+
+ const balancePost = await token.balanceOf(colony.address);
+ expect(balancePost.sub(balancePre)).to.eq.BN(WAD);
+
+ const tokenSupply = await token.totalSupply();
+ expect(tokenSupply).to.eq.BN(WAD);
+ });
+
it("can claim tokenIssuanceRate at high frequencies", async () => {
const balancePre = await token.balanceOf(colony.address);
diff --git a/test/extensions/voting-rep.js b/test/extensions/voting-rep.js
index f80c77198d..7a276880fe 100644
--- a/test/extensions/voting-rep.js
+++ b/test/extensions/voting-rep.js
@@ -26,6 +26,7 @@ import {
setupMetaColonyWithLockedCLNYToken,
setupRandomColony,
giveUserCLNYTokensAndStake,
+ getMetaTransactionParameters,
} from "../../helpers/test-data-generator";
import { setupEtherRouter } from "../../helpers/upgradable-contracts";
@@ -362,6 +363,21 @@ contract("Voting Reputation", (accounts) => {
expect(motion.skillId).to.eq.BN(domain1.skillId);
});
+ it("can create a root motion via metatransaction", async () => {
+ const action = await encodeTxData(colony, "mintTokens", [WAD]);
+ const txData = await voting.contract.methods
+ .createMotion(1, UINT256_MAX.toString(), ADDRESS_ZERO, action, domain1Key, domain1Value, domain1Mask, domain1Siblings)
+ .encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER2, voting.address);
+
+ await voting.executeMetaTransaction(USER2, txData, r, s, v, { from: USER1 });
+
+ const motionId = await voting.getMotionCount();
+ const motion = await voting.getMotion(motionId);
+ expect(motion.skillId).to.eq.BN(domain1.skillId);
+ });
+
it("can create a domain motion in the root domain", async () => {
// Create motion in domain of action (1)
const action = await encodeTxData(colony, "makeTask", [1, UINT256_MAX, FAKE, 1, 0, 0]);
diff --git a/test/extensions/whitelist.js b/test/extensions/whitelist.js
index 93bdf83f1a..1d881dcc10 100644
--- a/test/extensions/whitelist.js
+++ b/test/extensions/whitelist.js
@@ -8,7 +8,12 @@ import { soliditySha3 } from "web3-utils";
import { UINT256_MAX, IPFS_HASH } from "../../helpers/constants";
import { setupEtherRouter } from "../../helpers/upgradable-contracts";
import { checkErrorRevert, web3GetCode } from "../../helpers/test-helper";
-import { setupColonyNetwork, setupRandomColony, setupMetaColonyWithLockedCLNYToken } from "../../helpers/test-data-generator";
+import {
+ setupColonyNetwork,
+ setupRandomColony,
+ setupMetaColonyWithLockedCLNYToken,
+ getMetaTransactionParameters,
+} from "../../helpers/test-data-generator";
const { expect } = chai;
chai.use(bnChai(web3.utils.BN));
@@ -169,6 +174,31 @@ contract("Whitelist", (accounts) => {
expect(status).to.be.true;
});
+ it("can make users sign an agreement via metatransaction", async () => {
+ await whitelist.initialise(false, IPFS_HASH);
+
+ let status;
+ let signature;
+
+ signature = await whitelist.getSignature(USER1);
+ expect(signature).to.be.false;
+
+ status = await whitelist.isApproved(USER1);
+ expect(status).to.be.false;
+
+ const txData = await whitelist.contract.methods.signAgreement(IPFS_HASH).encodeABI();
+
+ const { r, s, v } = await getMetaTransactionParameters(txData, USER1, whitelist.address);
+
+ await whitelist.executeMetaTransaction(USER1, txData, r, s, v, { from: USER0 });
+
+ signature = await whitelist.getSignature(USER1);
+ expect(signature).to.be.true;
+
+ status = await whitelist.isApproved(USER1);
+ expect(status).to.be.true;
+ });
+
it("cannot accept a bad agreement", async () => {
await whitelist.initialise(false, IPFS_HASH);