Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,4 @@ build/
migrations/*.js
migrations/*.js.map
migrations/*.d.ts
/migrations/migrations/
19 changes: 15 additions & 4 deletions contracts/CPKFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ pragma solidity >=0.5.0 <0.7.0;
import { Enum } from "@gnosis.pm/safe-contracts/contracts/common/Enum.sol";
import { Proxy } from "@gnosis.pm/safe-contracts/contracts/proxies/Proxy.sol";
import { GnosisSafe } from "@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol";
import "./GsnModule.sol";

contract CPKFactory {
contract CPKFactory is BaseRelayRecipient {
event ProxyCreation(Proxy proxy);
GsnModule public gsnModule = new GsnModule();

constructor(address forwarder) public {
gsnModule.setForwarder(forwarder);
trustedForwarder=forwarder;
}

function proxyCreationCode() external pure returns (bytes memory) {
return type(Proxy).creationCode;
Expand All @@ -25,7 +32,7 @@ contract CPKFactory {
{
GnosisSafe proxy;
bytes memory deploymentData = abi.encodePacked(type(Proxy).creationCode, abi.encode(masterCopy));
bytes32 salt = keccak256(abi.encode(msg.sender, saltNonce));
bytes32 salt = keccak256(abi.encode(_msgSender(), saltNonce));
// solium-disable-next-line security/no-inline-assembly
assembly {
proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)
Expand All @@ -35,15 +42,19 @@ contract CPKFactory {
{
address[] memory tmp = new address[](1);
tmp[0] = address(this);
proxy.setup(tmp, 1, address(0), "", fallbackHandler, address(0), 0, address(0));
address _fallbackHandler = fallbackHandler;
proxy.setup(tmp, 1,
address(gsnModule), //address for delegateCall
abi.encodeWithSignature("setup(address)", gsnModule),
_fallbackHandler, address(0), 0, address(0));
}

execTransactionSuccess = proxy.execTransaction(to, value, data, operation, 0, 0, 0, address(0), address(0),
abi.encodePacked(uint(address(this)), uint(0), uint8(1)));

proxy.execTransaction(
address(proxy), 0,
abi.encodeWithSignature("swapOwner(address,address,address)", address(1), address(this), msg.sender),
abi.encodeWithSignature("swapOwner(address,address,address)", address(1), address(this), _msgSender()),
Enum.Operation.Call,
0, 0, 0, address(0), address(0),
abi.encodePacked(uint(address(this)), uint(0), uint8(1))
Expand Down
28 changes: 28 additions & 0 deletions contracts/GsnModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
pragma solidity >=0.5.0 <0.7.0;

import { GnosisSafe } from "@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol";
import { Module } from "@gnosis.pm/safe-contracts/contracts/base/Module.sol";
import { Enum } from "@gnosis.pm/safe-contracts/contracts/common/Enum.sol";
import { BaseRelayRecipient } from "./gsn/BaseRelayRecipient.sol";

contract GsnModule is Module, BaseRelayRecipient {

function setForwarder(address forwarder) public {
require(trustedForwarder==address(0), "Forwrader already set");
trustedForwarder=forwarder;
}


function execCall(GnosisSafe proxy, address to, bytes calldata data,Enum.Operation operation) external {
require( proxy.isOwner(_msgSender()), "execCall: not owner");
proxy.execTransactionFromModule(to, 0, data, operation);
}

//called as delegatecall during setup, to add this GsnModule as a module.
// since its a delegateCall, "this" is the GnosisSafe itself, and the module (real this) is a parameter...
function setup(GsnModule gsnModule) external {

GnosisSafe(address(uint160(address(this)))).enableModule(gsnModule);
}

}
50 changes: 50 additions & 0 deletions contracts/gsn/BaseRelayRecipient.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier:MIT
pragma solidity >=0.5;

import "./interfaces/IRelayRecipient.sol";
import "./interfaces/IKnowForwarderAddress.sol";
/**
* A base contract to be inherited by any contract that want to receive relayed transactions
* A subclass must use "_msgSender()" instead of "msg.sender"
*/
contract BaseRelayRecipient is IRelayRecipient, IKnowForwarderAddress {

/// the TrustedForwarder singleton we accept calls from.
// we trust it to verify the caller's signature, and pass the caller's address as last 20 bytes
address internal trustedForwarder;

function getTrustedForwarder() public view returns(address) {
return trustedForwarder;
}

/*
* require a function to be called through GSN only
*/
modifier trustedForwarderOnly() {
require(msg.sender == address(trustedForwarder), "Function can only be called through trustedForwarder");
_;
}

function isTrustedForwarder(address forwarder) public view returns(bool) {
return forwarder == trustedForwarder;
}

/**
* return the sender of this call.
* if the call came through our trusted forwarder, return the original sender.
* otherwise, return `msg.sender`.
* should be used in the contract anywhere instead of msg.sender
*/
function _msgSender() internal view returns (address payable ret) {
if (msg.data.length >= 24 && isTrustedForwarder(msg.sender)) {
// At this point we know that the sender is a trusted forwarder,
// so we trust that the last bytes of msg.data are the verified sender address.
// extract sender address from the end of msg.data
assembly {
ret := shr(96,calldataload(sub(calldatasize(),20)))
}
} else {
return msg.sender;
}
}
}
12 changes: 12 additions & 0 deletions contracts/gsn/interfaces/IKnowForwarderAddress.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier:MIT
pragma solidity >=0.5;

interface IKnowForwarderAddress {

/**
* return the forwarder we trust to forward relayed transactions to us.
* the forwarder is required to verify the sender's signature, and verify
* the call is not a replay.
*/
function getTrustedForwarder() external view returns(address);
}
26 changes: 26 additions & 0 deletions contracts/gsn/interfaces/IRelayRecipient.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier:MIT
pragma solidity >=0.5;

/**
* a contract must implement this interface in order to support relayed transaction.
* It is better to inherit the BaseRelayRecipient as its implementation.
*/
contract IRelayRecipient {

/**
* return if the forwarder is trusted to forward relayed transactions to us.
* the forwarder is required to verify the sender's signature, and verify
* the call is not a replay.
*/
function isTrustedForwarder(address forwarder) public view returns(bool);

/**
* return the sender of this call.
* if the call came through our trusted forwarder, then the real sender is appended as the last 20 bytes
* of the msg.data.
* otherwise, return `msg.sender`
* should be used in the contract anywhere instead of msg.sender
*/
function _msgSender() internal view returns (address payable);

}
8 changes: 7 additions & 1 deletion migrations/1-deploy-contracts.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import fs from 'fs';
const forwarder = JSON.parse(fs.readFileSync(__dirname+'/../build/gsn/Forwarder.json', 'utf-8')).address

module.exports = function(deployer: Truffle.Deployer, network: string) {
const deploy = (
name: string
): Truffle.Deployer => deployer.deploy(artifacts.require(name as any));

['Migrations', 'CPKFactory'].forEach(deploy);
['Migrations'].forEach(deploy);

(deployer.deploy as any)(artifacts.require('CPKFactory'), forwarder).catch(console.log);

if (network === 'test' || network === 'local') {
[
Expand All @@ -16,6 +21,7 @@ module.exports = function(deployer: Truffle.Deployer, network: string) {
'ConditionalTokens'
].forEach(deploy);
}

} as Truffle.Migration;

export {};
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"description": "Enable batched transactions and contract account interactions using a unique deterministic Gnosis Safe.",
"main": "src/index.ts",
"scripts": {
"gsn-ganache": "run-with-testrpc 'gsn start'",
"gsn-ganache": "run-with-testrpc -e 10000 'gsn start'",
"generate-types": "typechain --target=truffle-v5 './build/contracts/*.json'",
"postinstall": "truffle compile && yarn generate-types",
"migrate": "tsc -p ./tsconfig.migrate.json --outDir ./migrations && truffle migrate --network local",
Expand Down Expand Up @@ -67,5 +69,9 @@
"wait-port": "^0.2.9",
"web3-1-2": "npm:web3@^1.2.6",
"web3-2-alpha": "npm:web3@^2.0.0-alpha.1"
},
"dependencies": {
"@opengsn/gsn": "^0.10.0",
"source-map-support": "^0.5.19"
}
}
66 changes: 66 additions & 0 deletions src/abis/CpkFactoryAbi.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,70 @@
[
{
"constant": true,
"inputs": [],
"name": "getTrustedForwarder",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "gsnModule",
"outputs": [
{
"internalType": "contract GsnModule",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "forwarder",
"type": "address"
}
],
"name": "isTrustedForwarder",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "forwarder",
"type": "address"
}
],
"name": "setForwarder",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
Expand Down
Loading