Updates for Safe v1.4.0

This commit is contained in:
James Zaki
2023-05-13 23:10:34 +01:00
parent 251b17f0bc
commit 7ad41a20b9
19 changed files with 1378 additions and 76 deletions

View File

@@ -0,0 +1,237 @@
[![Build Status](https://travis-ci.org/gnosis/mock-contract.svg?branch=master)](https://travis-ci.org/gnosis/mock-contract)
# MockContract
Simple Solidity contract to mock dependencies in truffle tests. It enables you to
- Make dependent contracts return predefined values for different methods and arguments
- Simulate exceptions such as `revert` and `outOfGas`
- Assert on how often a dependency is called
*MockContract* allows for all of that without having to write a separate test contract each time.
# Usage in your project
Install module from npm
```bash
npm i -D @gnosis.pm/mock-contract
```
Enable compilation of MockContract
* Add a new `Imports.sol` to the `contracts` folder the project
* Copy the following code into `Imports.sol`:
```js
pragma solidity ^0.6.0;
// We import the contract so truffle compiles it, and we have the ABI
// available when working from truffle console.
import "@gnosis.pm/mock-contract/contracts/MockContract.sol";
```
* Use in javascript unit test:
```
const MockContract = artifacts.require("./MockContract.sol")
// Instantiate mock and make it return true for any invocation
const mock = await MockContract.new()
await mock.givenAnyReturnBool(true)
// instantiate "object under test" with mocked contract.
const contractToTest = await ComplextContract.new(mock.address)
```
# Step by Step Example
Let's assume we want to test the following smart contract, which implements a simple bidding procedure:
```js
pragma solidity ^0.6.0;
import "./Token.sol";
/**
* Contract that stores the highest bidder while securing payment from a given ERC20 Token.
* Upon each bid the cost of bidding is incremented.
*/
contract SimpleAuction {
address public captor; // Address of the highest bidder
Token private token;
uint256 private cost;
constructor(Token _token) public {
token = _token;
}
function bid() public {
if (token.transferFrom(msg.sender, this, cost + 1)) {
require(token.transfer(captor, cost), "Refund failed");
captor = msg.sender;
cost += 1;
}
}
}
```
If we were to write unit tests for this class, we would have to provide an implementation of an ERC20 token contract. There are commonly two ways to deal with that:
1. Use the real ERC20 token contract and configure it in a way that it will work for the test (e.g. call `token.approve` before bidding)
2. Implement a fake Token contract that instead of real logic contains dummy implementations (e.g. `return true` for everything)
The problem with 1) is that the logic required to make our dependency behave in the intended way can be very complex and incurs additional maintenance work. It also doesn't isolate our tests - instead of only testing the unit under test it is testing the integration of multiple components.
Solution 2) requires writing a *Fake* contract for each dependency. This takes time and pollutes the repository and migration files with a lot of non-production code.
## Mocking General Interactions
*MockContract* can act as a generic fake object for any type of contract. We can tell our mock what it should return upon certain invocations. For the example above, assume we want to write a test case where ERC20 transfers work just fine:
```js
const MockContract = artifacts.require("./MockContract.sol")
const SimpleAuction = artifacts.require("./SimpleAuction.sol")
...
it('updates the captor', async () => {
const mock = await MockContract.new()
const auction = await SimpleAuction.new(mock.address)
const trueEncoded = web3.eth.abi.encodeParameter("bool", true)
await mock.givenAnyReturnBool(trueEncoded)
await auction.bid({from: accounts[0]})
assert.equal(accounts[0], await auction.captor.call())
})
```
In particular `await mock.givenAnyReturnBool(true)` will make it so that mock returns `true` on any method invocation.
A plain mock without any expectations will return *nullish* values by default (e.g. `false` for bool, `0` for uint, etc).
There are convenience methods for other types such as `givenAnyReturnAddress` or `givenAnyReturnUint`. The full mock interface can be found [here](https://github.com/fleupold/mock-contract/blob/master/contracts/MockContract.sol#L3).
## Mocking Methods Individually
Now let's assume we want to test that the bid gets reverted if `token.transfer` succeeds but `token.transferFrom` fails:
```js
const Token = artifacts.require("./Token.sol")
...
it('should fail if we fail to refund', async () => {
const mock = await MockContract.new()
const auction = await SimpleAuction.new(mock.address)
const token = await Token.new();
const transferFrom = token.contract.methods.transferFrom(0, 0, 0).encodeABI() // arguments don't matter
const transfer = token.contract.methods.transfer(0,0).encodeABI() // arguments don't matter
await mock.givenMethodReturnBool(transferFrom, true)
await mock.givenMethodReturnBool(transfer, false)
try {
await auction.bid({from: accounts[1]})
assert.fail("Should have reverted")
} catch (e) {}
})
```
Different methods have different ABI encodings. `mock.givenMethodReturnBool(bytes, boolean)` takes the ABI encoded methodId as a first parameter and will only replace behavior for this method. There are two ways to construct the methodId. We recommend using the `encodeABI` call on the original contract's ABI:
```js
// Arguments do not matter, mock will only extract methodId
const transferFrom = token.contract.transferFrom(0, 0, 0).encodeABI()
```
We could also create it manually using e.g.:
```js
const transferFrom = web3.sha3("transferFrom(address,address,uint256)").slice(0,10) // first 4 bytes
```
However, the latter approach is not type-safe and can lead to unexpected test behavior if the ABI on the original contract changes. The first approach would give a much more descriptive compilation error in that case.
Again there are convenience functions for other return types (e.g. `givenMethoReturnUint`).
Mocked methods will take priority over mocks using *any*.
## Mocking Methods & Arguments
We can also specify different behaviors for when the same method is called with different arguments:
```js
it('Keeps the old bidder if the new bidder fails to transfer', async () => {
...
const transferFromA = token.contract.methods.transferFrom(accounts[0], auction.address, 1).encodeABI()
const transferFromB = token.contract.methods.transferFrom(accounts[1], auction.address, 2).encodeABI()
await mock.givenCalldataReturnBool(transferFromA, true)
await mock.givenCalldataReturnBool(transferFromB, false)
await auction.bid({from: accounts[0]})
await auction.bid({from: accounts[1]})
assert.equal(accounts[0], await auction.captor.call())
})
```
This time we need to provide the full `calldata`. We can easily use the original contract's ABI `encodeABI`call to generate it. Again, convenience functions for other return types exist (e.g. `givenMethoReturnUint`).
Mocked calls with exact calldata will takes priority over *method* mocks and *any* mocks.
## Simulating Failure
We can also simulate EVM exceptions using `MockContract`. All methods are available for *any*, *method* and *calldata* specific calls:
```js
// Revert upon any invocation on mock without a specific message
await mock.givenAnyRevert()
// Revert upon any invocation of `methodId` with the specific message
await mock.givenMethodRevertWithMessage(methodId, "Some specific message")
// Run out of gas, if mock is called with `calldata`
await mock.givenCalldataRunOutOfGas(calldata)
```
## Inspect Invocations
It can sometime be useful to see how often a dependency has been called during a test-case. E.g. we might want to assert that `transfer` is not called if `transferFrom` failed in the first place:
```js
it('only does the second transfer if the first transfer succeed', async () => {
...
await mock.givenAnyReturnBool(false)
await auction.bid()
const transfer = token.contract.methods.transfer(0,0).encodeABI()
const invocationCount = await mock.invocationCountForMethod.call(transfer)
assert.equal(0, invocationCount)
})
```
We can inspect the total invocation of `mock` for all methods combined using `await invocationCount.call()` and for individual arguments using `await invocationCountForCalldata.call(calldata)`.
## Resetting `mock`
We can override existing behavior throughout the lifetime of the test:
```js
await mock.givenAnyReturnBool(true) // will return true from now on
await auction.bid()
await mock.givenAnyReturnBool(false) // will return false from now on
await auction.bid()
```
Note that previously specified *method* and *calldata* based behavior will be unaffected by overriding *any* mock behavior.
To completely reset all behavior and clear invocation counts, we can call:
```js
await mock.reset()
```
## Complex `return` types
If the methods for returning the most commonly used types are not enough, we can manually ABI encode our responses with arbitrary solidity types:
```js
const hello_world = web3.eth.abi.encodeParameter("string", 'Hello World!')
await mock.givenAnyReturn(hello_world);
```
---------------------------------------
*This is a work in progress and feedback is highly appreciated. Please open an issue on GitHub and/or submit a pull request.*

View File

@@ -0,0 +1,15 @@
pragma solidity ^0.8.0;
/**
* @dev Used for unit testing MockContract functionality.
*/
interface ComplexInterface {
function methodA() external;
function methodB() external;
function acceptAdressUintReturnBool(address recipient, uint amount) external returns (bool);
function acceptUintReturnString(uint) external returns (string memory);
function acceptUintReturnBool(uint) external returns (bool);
function acceptUintReturnUint(uint) external returns (uint);
function acceptUintReturnAddress(uint) external returns (address);
function acceptUintReturnUintView(uint) external view returns (uint);
}

View File

@@ -0,0 +1,27 @@
pragma solidity ^0.8.0;
import './ComplexInterface.sol';
contract ExampleContractUnderTest {
ComplexInterface complexInterface;
constructor(address _complexInterface) public {
complexInterface = ComplexInterface(_complexInterface);
}
function callMockedFunction3Times() public view returns (bool) {
complexInterface.acceptUintReturnUintView(1);
complexInterface.acceptUintReturnUintView(1);
complexInterface.acceptUintReturnUintView(1);
return true;
}
function callMethodThatReturnsAddress() public returns (address) {
address foo = complexInterface.acceptUintReturnAddress(1);
return foo;
}
function callMethodThatReturnsBool() public returns (bool) {
return complexInterface.acceptUintReturnBool(1);
}
}

View File

@@ -0,0 +1,373 @@
pragma solidity ^0.8.0;
interface MockInterface {
/**
* @dev After calling this method, the mock will return `response` when it is called
* with any calldata that is not mocked more specifically below
* (e.g. using givenMethodReturn).
* @param response ABI encoded response that will be returned if method is invoked
*/
function givenAnyReturn(bytes calldata response) external;
function givenAnyReturnBool(bool response) external;
function givenAnyReturnUint(uint response) external;
function givenAnyReturnAddress(address response) external;
function givenAnyRevert() external;
function givenAnyRevertWithMessage(string calldata message) external;
function givenAnyRunOutOfGas() external;
/**
* @dev After calling this method, the mock will return `response` when the given
* methodId is called regardless of arguments. If the methodId and arguments
* are mocked more specifically (using `givenMethodAndArguments`) the latter
* will take precedence.
* @param method ABI encoded methodId. It is valid to pass full calldata (including arguments). The mock will extract the methodId from it
* @param response ABI encoded response that will be returned if method is invoked
*/
function givenMethodReturn(bytes calldata method, bytes calldata response) external;
function givenMethodReturnBool(bytes calldata method, bool response) external;
function givenMethodReturnUint(bytes calldata method, uint response) external;
function givenMethodReturnAddress(bytes calldata method, address response) external;
function givenMethodRevert(bytes calldata method) external;
function givenMethodRevertWithMessage(bytes calldata method, string calldata message) external;
function givenMethodRunOutOfGas(bytes calldata method) external;
/**
* @dev After calling this method, the mock will return `response` when the given
* methodId is called with matching arguments. These exact calldataMocks will take
* precedence over all other calldataMocks.
* @param call ABI encoded calldata (methodId and arguments)
* @param response ABI encoded response that will be returned if contract is invoked with calldata
*/
function givenCalldataReturn(bytes calldata call, bytes calldata response) external;
function givenCalldataReturnBool(bytes calldata call, bool response) external;
function givenCalldataReturnUint(bytes calldata call, uint response) external;
function givenCalldataReturnAddress(bytes calldata call, address response) external;
function givenCalldataRevert(bytes calldata call) external;
function givenCalldataRevertWithMessage(bytes calldata call, string calldata message) external;
function givenCalldataRunOutOfGas(bytes calldata call) external;
/**
* @dev Returns the number of times anything has been called on this mock since last reset
*/
function invocationCount() external returns (uint);
/**
* @dev Returns the number of times the given method has been called on this mock since last reset
* @param method ABI encoded methodId. It is valid to pass full calldata (including arguments). The mock will extract the methodId from it
*/
function invocationCountForMethod(bytes calldata method) external returns (uint);
/**
* @dev Returns the number of times this mock has been called with the exact calldata since last reset.
* @param call ABI encoded calldata (methodId and arguments)
*/
function invocationCountForCalldata(bytes calldata call) external returns (uint);
/**
* @dev Resets all mocked methods and invocation counts.
*/
function reset() external;
}
/**
* Implementation of the MockInterface.
*/
contract MockContract is MockInterface {
enum MockType { Return, Revert, OutOfGas }
bytes32 public constant MOCKS_LIST_START = hex"01";
bytes public constant MOCKS_LIST_END = "0xff";
bytes32 public constant MOCKS_LIST_END_HASH = keccak256(MOCKS_LIST_END);
bytes4 public constant SENTINEL_ANY_MOCKS = hex"01";
bytes public constant DEFAULT_FALLBACK_VALUE = abi.encode(false);
// A linked list allows easy iteration and inclusion checks
mapping(bytes32 => bytes) calldataMocks;
mapping(bytes => MockType) calldataMockTypes;
mapping(bytes => bytes) calldataExpectations;
mapping(bytes => string) calldataRevertMessage;
mapping(bytes32 => uint) calldataInvocations;
mapping(bytes4 => bytes4) methodIdMocks;
mapping(bytes4 => MockType) methodIdMockTypes;
mapping(bytes4 => bytes) methodIdExpectations;
mapping(bytes4 => string) methodIdRevertMessages;
mapping(bytes32 => uint) methodIdInvocations;
MockType fallbackMockType;
bytes fallbackExpectation = DEFAULT_FALLBACK_VALUE;
string fallbackRevertMessage;
uint invocations;
uint resetCount;
constructor() public {
calldataMocks[MOCKS_LIST_START] = MOCKS_LIST_END;
methodIdMocks[SENTINEL_ANY_MOCKS] = SENTINEL_ANY_MOCKS;
}
function trackCalldataMock(bytes memory call) private {
bytes32 callHash = keccak256(call);
if (calldataMocks[callHash].length == 0) {
calldataMocks[callHash] = calldataMocks[MOCKS_LIST_START];
calldataMocks[MOCKS_LIST_START] = call;
}
}
function trackMethodIdMock(bytes4 methodId) private {
if (methodIdMocks[methodId] == 0x0) {
methodIdMocks[methodId] = methodIdMocks[SENTINEL_ANY_MOCKS];
methodIdMocks[SENTINEL_ANY_MOCKS] = methodId;
}
}
function _givenAnyReturn(bytes memory response) internal {
fallbackMockType = MockType.Return;
fallbackExpectation = response;
}
function givenAnyReturn(bytes calldata response) override external {
_givenAnyReturn(response);
}
function givenAnyReturnBool(bool response) override external {
uint flag = response ? 1 : 0;
_givenAnyReturn(uintToBytes(flag));
}
function givenAnyReturnUint(uint response) override external {
_givenAnyReturn(uintToBytes(response));
}
function givenAnyReturnAddress(address response) override external {
_givenAnyReturn(uintToBytes(uint256(uint160(response))));
}
function givenAnyRevert() override external {
fallbackMockType = MockType.Revert;
fallbackRevertMessage = "";
}
function givenAnyRevertWithMessage(string calldata message) override external {
fallbackMockType = MockType.Revert;
fallbackRevertMessage = message;
}
function givenAnyRunOutOfGas() override external {
fallbackMockType = MockType.OutOfGas;
}
function _givenCalldataReturn(bytes memory call, bytes memory response) private {
calldataMockTypes[call] = MockType.Return;
calldataExpectations[call] = response;
trackCalldataMock(call);
}
function givenCalldataReturn(bytes calldata call, bytes calldata response) override external {
_givenCalldataReturn(call, response);
}
function givenCalldataReturnBool(bytes calldata call, bool response) override external {
uint flag = response ? 1 : 0;
_givenCalldataReturn(call, uintToBytes(flag));
}
function givenCalldataReturnUint(bytes calldata call, uint response) override external {
_givenCalldataReturn(call, uintToBytes(response));
}
function givenCalldataReturnAddress(bytes calldata call, address response) override external {
_givenCalldataReturn(call, uintToBytes(uint256(uint160(response))));
}
function _givenMethodReturn(bytes memory call, bytes memory response) private {
bytes4 method = bytesToBytes4(call);
methodIdMockTypes[method] = MockType.Return;
methodIdExpectations[method] = response;
trackMethodIdMock(method);
}
function givenMethodReturn(bytes calldata call, bytes calldata response) override external {
_givenMethodReturn(call, response);
}
function givenMethodReturnBool(bytes calldata call, bool response) override external {
uint flag = response ? 1 : 0;
_givenMethodReturn(call, uintToBytes(flag));
}
function givenMethodReturnUint(bytes calldata call, uint response) override external {
_givenMethodReturn(call, uintToBytes(response));
}
function givenMethodReturnAddress(bytes calldata call, address response) override external {
_givenMethodReturn(call, uintToBytes(uint256(uint160(response))));
}
function givenCalldataRevert(bytes calldata call) override external {
calldataMockTypes[call] = MockType.Revert;
calldataRevertMessage[call] = "";
trackCalldataMock(call);
}
function givenMethodRevert(bytes calldata call) override external {
bytes4 method = bytesToBytes4(call);
methodIdMockTypes[method] = MockType.Revert;
trackMethodIdMock(method);
}
function givenCalldataRevertWithMessage(bytes calldata call, string calldata message) override external {
calldataMockTypes[call] = MockType.Revert;
calldataRevertMessage[call] = message;
trackCalldataMock(call);
}
function givenMethodRevertWithMessage(bytes calldata call, string calldata message) override external {
bytes4 method = bytesToBytes4(call);
methodIdMockTypes[method] = MockType.Revert;
methodIdRevertMessages[method] = message;
trackMethodIdMock(method);
}
function givenCalldataRunOutOfGas(bytes calldata call) override external {
calldataMockTypes[call] = MockType.OutOfGas;
trackCalldataMock(call);
}
function givenMethodRunOutOfGas(bytes calldata call) override external {
bytes4 method = bytesToBytes4(call);
methodIdMockTypes[method] = MockType.OutOfGas;
trackMethodIdMock(method);
}
function invocationCount() override external returns (uint) {
return invocations;
}
function invocationCountForMethod(bytes calldata call) override external returns (uint) {
bytes4 method = bytesToBytes4(call);
return methodIdInvocations[keccak256(abi.encodePacked(resetCount, method))];
}
function invocationCountForCalldata(bytes calldata call) override external returns (uint) {
return calldataInvocations[keccak256(abi.encodePacked(resetCount, call))];
}
function reset() override external {
// Reset all exact calldataMocks
bytes memory nextMock = calldataMocks[MOCKS_LIST_START];
bytes32 mockHash = keccak256(nextMock);
// We cannot compary bytes
while(mockHash != MOCKS_LIST_END_HASH) {
// Reset all mock maps
calldataMockTypes[nextMock] = MockType.Return;
calldataExpectations[nextMock] = hex"";
calldataRevertMessage[nextMock] = "";
// Set next mock to remove
nextMock = calldataMocks[mockHash];
// Remove from linked list
calldataMocks[mockHash] = "";
// Update mock hash
mockHash = keccak256(nextMock);
}
// Clear list
calldataMocks[MOCKS_LIST_START] = MOCKS_LIST_END;
// Reset all any calldataMocks
bytes4 nextAnyMock = methodIdMocks[SENTINEL_ANY_MOCKS];
while(nextAnyMock != SENTINEL_ANY_MOCKS) {
bytes4 currentAnyMock = nextAnyMock;
methodIdMockTypes[currentAnyMock] = MockType.Return;
methodIdExpectations[currentAnyMock] = hex"";
methodIdRevertMessages[currentAnyMock] = "";
nextAnyMock = methodIdMocks[currentAnyMock];
// Remove from linked list
methodIdMocks[currentAnyMock] = 0x0;
}
// Clear list
methodIdMocks[SENTINEL_ANY_MOCKS] = SENTINEL_ANY_MOCKS;
fallbackExpectation = DEFAULT_FALLBACK_VALUE;
fallbackMockType = MockType.Return;
invocations = 0;
resetCount += 1;
}
function useAllGas() private {
while(true) {
bool s;
assembly {
//expensive call to EC multiply contract
s := call(sub(gas(), 2000), 6, 0, 0x0, 0xc0, 0x0, 0x60)
}
}
}
function bytesToBytes4(bytes memory b) private pure returns (bytes4) {
bytes4 out;
for (uint i = 0; i < 4; i++) {
out |= bytes4(b[i] & 0xFF) >> (i * 8);
}
return out;
}
function uintToBytes(uint256 x) private pure returns (bytes memory b) {
b = new bytes(32);
assembly { mstore(add(b, 32), x) }
}
function updateInvocationCount(bytes4 methodId, bytes memory originalMsgData) public {
require(msg.sender == address(this), "Can only be called from the contract itself");
invocations += 1;
methodIdInvocations[keccak256(abi.encodePacked(resetCount, methodId))] += 1;
calldataInvocations[keccak256(abi.encodePacked(resetCount, originalMsgData))] += 1;
}
fallback () payable external {
bytes4 methodId;
assembly {
methodId := calldataload(0)
}
// First, check exact matching overrides
if (calldataMockTypes[msg.data] == MockType.Revert) {
revert(calldataRevertMessage[msg.data]);
}
if (calldataMockTypes[msg.data] == MockType.OutOfGas) {
useAllGas();
}
bytes memory result = calldataExpectations[msg.data];
// Then check method Id overrides
if (result.length == 0) {
if (methodIdMockTypes[methodId] == MockType.Revert) {
revert(methodIdRevertMessages[methodId]);
}
if (methodIdMockTypes[methodId] == MockType.OutOfGas) {
useAllGas();
}
result = methodIdExpectations[methodId];
}
// Last, use the fallback override
if (result.length == 0) {
if (fallbackMockType == MockType.Revert) {
revert(fallbackRevertMessage);
}
if (fallbackMockType == MockType.OutOfGas) {
useAllGas();
}
result = fallbackExpectation;
}
// Record invocation as separate call so we don't rollback in case we are called with STATICCALL
(, bytes memory r) = address(this).call{gas: 100000}(abi.encodeWithSignature("updateInvocationCount(bytes4,bytes)", methodId, msg.data));
assert(r.length == 0);
assembly {
return(add(0x20, result), mload(result))
}
}
}

View File

@@ -0,0 +1,32 @@
{
"name": "@gnosis.pm/mock-contract",
"version": "4.0.0",
"description": "Simple Solidity contract to mock dependent contracts in truffle tests.",
"main": "truffle-config.js",
"files": [
"contracts",
"test"
],
"scripts": {
"test-norpc": "truffle test",
"test": "run-with-testrpc -l 20000000 --noVMErrorsOnRPCResponse true 'truffle test'"
},
"keywords": [
"solidity",
"mock",
"unit-testing",
"ethereum",
"truffle",
"stub",
"contract"
],
"homepage": "https://github.com/fleupold/mock-contract",
"author": "Felix Leupold",
"license": "ISC",
"devDependencies": {
"run-with-testrpc": "^0.3.0",
"truffle": "^5.1"
},
"dependencies": {
}
}

View File

@@ -0,0 +1,547 @@
const utils = require('./utils')
const MockContract = artifacts.require("./MockContract.sol")
const ComplexInterface = artifacts.require("./ComplexInterface.sol")
const ExampleContractUnderTest = artifacts.require("./ExampleContractUnderTest.sol")
contract('MockContract', function(accounts) {
describe("cleanState", function() {
it("should return null if not mocked", async function() {
const mock = await MockContract.new()
const complex = await ComplexInterface.at(mock.address)
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000000", 10);
assert.equal(result, false)
});
it("should return null if not mocked when called by contract under test", async function() {
const mock = await MockContract.new()
const exampleContract = await ExampleContractUnderTest.new(mock.address);
result = await exampleContract.callMethodThatReturnsBool.call();
assert.equal(result, false)
});
});
describe("givenAnyReturn", function() {
it("should return the mocked value", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
await mock.givenAnyReturn(web3.eth.abi.encodeParameter("bool", true))
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000000", 10)
assert.equal(result, true)
// Check that other methods also return true
result = await complex.acceptUintReturnBool.call(10);
assert.equal(result, true)
// Check that we can reset
await mock.reset()
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000000", 10)
assert.equal(result, false)
// Check convenience methods
await mock.givenAnyReturnBool(true)
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000000", 10)
assert.equal(result, true)
await mock.givenAnyReturnUint(42)
result = await complex.acceptUintReturnUint.call(7);
assert.equal(result, 42)
await mock.givenAnyReturnAddress(accounts[0])
result = await complex.acceptUintReturnAddress.call(7);
assert.equal(result, accounts[0])
});
});
describe("givenAnyRevert", function() {
it("should revert if mocked", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
await mock.givenAnyRevert();
// On error it should return the error message for a call
const encoded = await complex.contract.methods.methodA().encodeABI();
error = await utils.getErrorMessage(complex.address, 0, encoded)
assert.equal(error, "")
await utils.assertRevert(complex.methodA())
// Check that other calls also error
await utils.assertRevert(complex.methodB())
// Check that we can reset revert
await mock.reset()
// Transaction should be successful
await complex.methodA()
});
});
describe("givenAnyRevertWithMessage", function() {
it("should revert if mocked and return message", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
await mock.givenAnyRevertWithMessage("This is Sparta!!!");
// On error it should return the error message for a call
const encoded = await complex.contract.methods.methodA().encodeABI();
error = await utils.getErrorMessage(complex.address, 0, encoded)
assert.equal(error, "This is Sparta!!!")
await utils.assertRevert(complex.methodA())
// Check that other calls also error
await utils.assertRevert(complex.methodB())
// Check that we can reset revert
await mock.reset()
// Transaction should be successful
await complex.methodA()
});
});
describe("givenAnyRunOutOfGas", function() {
it("should run out of gas if mocked", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
await mock.givenAnyRunOutOfGas()
await utils.assertOutOfGas(complex.methodA())
// Check that other calls also run out of gas
await utils.assertOutOfGas(complex.methodB())
// Check that we can reset revert
await mock.reset()
// Transaction should be successful
await complex.methodA()
});
});
describe("givenCalldataReturn", function() {
it("should return the mocked value", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
let encoded = await complex.contract.methods.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10).encodeABI()
await mock.givenCalldataReturn(encoded, web3.eth.abi.encodeParameter("bool", true))
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000000", 10)
assert.equal(result, true)
// Check that other calls return default
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000001", 10);
assert.equal(result, false)
// Check that we can reset
await mock.reset()
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000000", 10)
assert.equal(result, false)
// Check convenience methods
await mock.givenCalldataReturnBool(encoded, "true")
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000000", 10)
assert.equal(result, true)
encoded = await complex.contract.methods.acceptUintReturnUint(7).encodeABI();
await mock.givenCalldataReturnUint(encoded, 42)
result = await complex.acceptUintReturnUint.call(7);
assert.equal(result, 42)
encoded = await complex.contract.methods.acceptUintReturnAddress(7).encodeABI();
await mock.givenCalldataReturnAddress(encoded, accounts[0])
result = await complex.acceptUintReturnAddress.call(7);
assert.equal(result, accounts[0])
});
it("should allow mocking the same method with different paramters", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
encodedA = await complex.contract.methods.acceptUintReturnUint(7).encodeABI();
encodedB = await complex.contract.methods.acceptUintReturnUint(8).encodeABI();
await mock.givenCalldataReturnUint(encodedA, 7)
await mock.givenCalldataReturnUint(encodedB, 8)
let result = await complex.acceptUintReturnUint.call(7)
assert.equal(7, result)
result = await complex.acceptUintReturnUint.call(8)
assert.equal(8, result)
});
it("should allow contract under test to call mocked method 3 times in 1 transaction", async function() {
const mock = await MockContract.new();
const exampleContract = await ExampleContractUnderTest.new(mock.address);
mock.givenAnyReturnUint(1)
const result = await exampleContract.callMockedFunction3Times()
assert.equal(result, true)
});
})
describe("givenCalldataRevert", function() {
it("should revert if mocked", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
const encoded = await complex.contract.methods.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10).encodeABI();
await mock.givenCalldataRevert(encoded);
// On error it should return the error message for a call
error = await utils.getErrorMessage(complex.address, 0, encoded)
assert.equal(error, "")
// Check that other calls return default
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000001", 10);
assert.equal(result, false)
await utils.assertRevert(complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10))
// Check that we can reset revert
await mock.reset()
// Transaction should be successful
await complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10)
});
});
describe("givenCalldataRevertWithMessage", function() {
it("should revert if mocked and return message", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
const encoded = await complex.contract.methods.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10).encodeABI();
await mock.givenCalldataRevertWithMessage(encoded, "This is Sparta!!!");
// On error it should return the error message for a call
error = await utils.getErrorMessage(complex.address, 0, encoded)
assert.equal(error, "This is Sparta!!!")
await utils.assertRevert(complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10))
// Check that other calls return default
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000001", 10);
assert.equal(result, false)
// Check that we can reset revert
await mock.reset()
// Transactions should be successful
await complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10)
});
});
describe("givenCalldataRunOutOfGas", function() {
it("should run out of gas if mocked", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
const encoded = await complex.contract.methods.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10).encodeABI();
await mock.givenCalldataRunOutOfGas(encoded);
// On error it should return the error message for a call
error = await utils.getErrorMessage(complex.address, 0, encoded)
assert.equal(error, "")
await utils.assertOutOfGas(complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10))
// Check that other calls return default
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000001", 10);
assert.equal(result, false)
// Check that we can reset revert
await mock.reset()
// Transaction should be successful
await complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10)
});
});
/*
* Tests for "any" functionality
*/
describe("givenMethodReturn", function() {
it("should return the mocked value", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
let methodId = await complex.contract.methods.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000",0).encodeABI();
await mock.givenMethodReturn(methodId, web3.eth.abi.encodeParameter("bool", true))
// Check transactions and calls
complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10)
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000000", 10)
assert.equal(result, true)
complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000001", 12)
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000001", 12)
assert.equal(result, true)
// Check that we can reset mock
await mock.reset()
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000000", 10)
assert.equal(result, false)
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000001", 12)
assert.equal(result, false)
// Check convenience methods
await mock.givenMethodReturnBool(methodId, true)
result = await complex.acceptAdressUintReturnBool.call("0x0000000000000000000000000000000000000000", 10)
assert.equal(result, true)
methodId = await complex.contract.methods.acceptUintReturnUint(0).encodeABI();
await mock.givenMethodReturnUint(methodId, 42)
result = await complex.acceptUintReturnUint.call(0);
assert.equal(result, 42)
methodId = await complex.contract.methods.acceptUintReturnAddress(0).encodeABI();
await mock.givenMethodReturnAddress(methodId, accounts[0])
result = await complex.acceptUintReturnAddress.call(0);
assert.equal(result, accounts[0])
});
it("should mock method returning an address which can be used in `contract under test`", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
const exampleContract = await ExampleContractUnderTest.new(mock.address);
const methodId = await complex.contract.methods.acceptUintReturnAddress(0).encodeABI();
await mock.givenMethodReturnAddress(methodId, accounts[0]);
await exampleContract.callMethodThatReturnsAddress();
});
});
describe("givenMethodRevert", function() {
it("should revert if mocked", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
const methodId = await complex.contract.methods.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000",0).encodeABI();
await mock.givenMethodRevert(methodId);
// On error it should return the error message for a call
var encoded = await complex.contract.methods.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10).encodeABI();
error = await utils.getErrorMessage(complex.address, 0, encoded)
assert.equal(error, "")
encoded = await complex.contract.methods.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000001", 12).encodeABI();
error = await utils.getErrorMessage(complex.address, 0, encoded)
assert.equal(error, "")
await utils.assertRevert(complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10))
await utils.assertRevert(complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000001", 12))
// Check that we can reset revert
await mock.reset()
// Transactions should be successful
await complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10)
await complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000001", 12)
});
});
describe("givenMethodRevertWithMessage", function() {
it("should revert if mocked and return message", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
const methodId = await complex.contract.methods.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000",0).encodeABI();
await mock.givenMethodRevertWithMessage(methodId, "This is Sparta!!!");
// On error it should return the error message for a call
var encoded = await complex.contract.methods.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10).encodeABI();
error = await utils.getErrorMessage(complex.address, 0, encoded)
assert.equal(error, "This is Sparta!!!")
encoded = await complex.contract.methods.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000001", 12).encodeABI();
error = await utils.getErrorMessage(complex.address, 0, encoded)
assert.equal(error, "This is Sparta!!!")
await utils.assertRevert(complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10))
await utils.assertRevert(complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000001", 12))
// Check that we can reset revert
await mock.reset()
// Transactions should be successful
await complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10)
await complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000001", 12)
});
});
describe("givenMethodRunOutOfGas", function() {
it("should run out of gas if mocked", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
const methodId = await complex.contract.methods.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000",0).encodeABI();
await mock.givenMethodRunOutOfGas(methodId);
// On error it should return the error message for a call
var encoded = await complex.contract.methods.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10).encodeABI();
error = await utils.getErrorMessage(complex.address, 0, encoded)
assert.equal(error, "")
encoded = await complex.contract.methods.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000001", 12).encodeABI();
error = await utils.getErrorMessage(complex.address, 0, encoded)
assert.equal(error, "")
await utils.assertOutOfGas(complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10))
await utils.assertOutOfGas(complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000001", 12))
// Check that we can reset revert
await mock.reset()
// Transactions should be successful
await complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000000", 10)
await complex.acceptAdressUintReturnBool("0x0000000000000000000000000000000000000001", 12)
});
});
describe("test mock priority", function() {
const methodId = web3.eth.abi.encodeFunctionSignature("acceptUintReturnString(uint256)");
const testSpecificMocks = async function (mock, complex) {
const encoded = await complex.contract.methods.acceptUintReturnString(42).encodeABI()
await mock.givenCalldataReturn(encoded, web3.eth.abi.encodeParameter("string","return specific"));
result = await complex.acceptUintReturnString.call(42);
// Specific mock should be prioritized over any mock
assert.equal(result, "return specific")
await mock.givenCalldataRevert(encoded);
await utils.assertRevert(complex.acceptUintReturnString(42))
await mock.givenCalldataRevertWithMessage(encoded, "revert specific");
error = await utils.getErrorMessage(complex.address, 0, encoded)
assert.equal(error, "revert specific")
await mock.givenCalldataRunOutOfGas(encoded);
await utils.assertOutOfGas(complex.acceptUintReturnString(42))
// Check that we can reset revert
await mock.reset()
// Transactions should be successful
const response = await complex.acceptUintReturnString.call(42)
assert.equal(response, "")
}
it("all specific mocks should be prioritized over return any mock", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
// No mock set
const response = await complex.acceptUintReturnString.call(42)
assert.equal(response, "")
// Fallback mock set
await mock.givenAnyReturn(web3.eth.abi.encodeParameter("string", "fallback"))
let result = await complex.acceptUintReturnString.call(42)
assert.equal(result, "fallback")
// MethodId mock set
await mock.givenMethodReturn(methodId, web3.eth.abi.encodeParameter("string", "methodId"));
result = await complex.acceptUintReturnString.call(42);
assert.equal(result, "methodId")
await testSpecificMocks(mock, complex)
});
it("all specific mocks should be prioritized over revert any mock", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
// No mock set
const response = await complex.acceptUintReturnString.call(42)
assert.equal(response, "")
const encoded = await complex.contract.methods.acceptUintReturnString(42).encodeABI()
// Fallback mock set
await mock.givenAnyRevertWithMessage('revert fallback')
await utils.assertRevert(complex.acceptUintReturnString(42))
error = await utils.getErrorMessage(complex.address, 0, encoded)
assert.equal(error, "revert fallback")
// MethodId mock set
await mock.givenMethodRevertWithMessage(methodId, "revert method");
await utils.assertRevert(complex.acceptUintReturnString(42))
error = await utils.getErrorMessage(complex.address, 0, encoded)
assert.equal(error, "revert method")
await testSpecificMocks(mock, complex)
});
it("all specific mocks should be prioritized over out of gas any mock", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
// No mock set
const response = await complex.acceptUintReturnString.call(42)
assert.equal(response, "")
// Fallback mock set
await mock.givenAnyReturn(web3.eth.abi.encodeParameter("string", "fallback"))
result = await complex.acceptUintReturnString.call(42);
assert.equal(result, "fallback")
// MethodId mock set
await mock.givenMethodRunOutOfGas(methodId);
await utils.assertOutOfGas(complex.acceptUintReturnString(42))
await testSpecificMocks(mock, complex)
});
});
describe("invocationCount", function() {
it("returns the correct invocation count", async function() {
const mock = await MockContract.new()
const complex = await ComplexInterface.at(mock.address)
const calldata = await complex.contract.methods.acceptUintReturnString(42).encodeABI()
// Initially everything at 0
let count = await mock.invocationCount.call()
assert.equal(count, 0)
count = await mock.invocationCountForMethod.call(calldata)
assert.equal(count, 0)
count = await mock.invocationCountForCalldata.call(calldata)
assert.equal(count, 0)
// Make a few calls and assert count
await complex.methodA();
await complex.acceptUintReturnString(42);
await complex.acceptUintReturnString(-1);
count = await mock.invocationCount.call()
assert.equal(count, 3)
count = await mock.invocationCountForMethod.call(calldata)
assert.equal(count, 2)
count = await mock.invocationCountForCalldata.call(calldata)
assert.equal(count, 1)
// After reset everything at 0 again
await mock.reset()
count = await mock.invocationCount.call()
assert.equal(count, 0)
count = await mock.invocationCountForMethod.call(calldata)
assert.equal(count, 0)
count = await mock.invocationCountForCalldata.call(calldata)
assert.equal(count, 0)
});
});
describe("givenMethodReturn for view functions", function() {
it("should return the mocked value", async function() {
const mock = await MockContract.new();
const complex = await ComplexInterface.at(mock.address)
let methodId = await complex.contract.methods.acceptUintReturnUintView(0).encodeABI();
await mock.givenMethodReturn(methodId, web3.eth.abi.encodeParameter("uint", 7))
result = await complex.acceptUintReturnUintView(0)
assert.equal(result.toNumber(), 7)
});
});
});

View File

@@ -0,0 +1,45 @@
async function assertRejects(q, msg, errorPredicate) {
let res, error = false
try {
res = await q
} catch(e) {
error = e
} finally {
if(!error)
assert.fail(res, null, msg)
else if (errorPredicate)
errorPredicate(error)
}
return res
}
async function assertRevert(q) {
const msg = "Should have reverted"
await assertRejects(q, msg, (err) => {
assert.ok(!err.message.includes("after consuming all gas"), msg)
})
}
async function assertOutOfGas(q) {
const msg = "Should have run out of gas"
await assertRejects(q, msg, (err) => {
assert.ok(err.message.includes("after consuming all gas"), msg)
})
}
async function getErrorMessage(to, value, data, from) {
let returnData = await web3.eth.call({to: to, from: from, value: value, data: data})
let returnBuffer = Buffer.from(returnData.slice(2), "hex")
if (returnBuffer.length > 4) {
return web3.eth.abi.decodeParameter("string", returnBuffer.slice(4).toString("hex"));
} else {
return "";
}
}
Object.assign(exports, {
assertRejects,
getErrorMessage,
assertOutOfGas,
assertRevert,
})

View File

@@ -0,0 +1,19 @@
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*"
}
},
compilers: {
solc: {
version: "^0.6.0",
settings: {
optimizer: {
enabled: true
},
}
}
}
};

View File

@@ -24,7 +24,7 @@
"url": "https://github.com/eth-infinitism/account-abstraction/issues"
},
"devDependencies": {
"@gnosis.pm/safe-contracts": "^1.3.0",
// "@gnosis.pm/safe-contracts": "^1.3.0",
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1"
}

Submodule account-abstraction/contracts/safe-contracts added at 4b9c46fcfe

View File

@@ -3,8 +3,8 @@ pragma solidity ^0.8.7;
/* solhint-disable no-inline-assembly */
import "@gnosis.pm/safe-contracts/contracts/handler/DefaultCallbackHandler.sol";
import "@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol";
import "../../safe-contracts/contracts/handler/DefaultCallbackHandler.sol";
import "../../safe-contracts/contracts/Safe.sol";
import "@openzeppelin/contracts/interfaces/IERC1271.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "../../interfaces/IAccount.sol";
@@ -13,8 +13,8 @@ import "./EIP4337Manager.sol";
using ECDSA for bytes32;
/**
* The GnosisSafe enables adding custom functions implementation to the Safe by setting a 'fallbackHandler'.
* This 'fallbackHandler' adds an implementation of 'validateUserOp' to the GnosisSafe.
* The Safe enables adding custom functions implementation to the Safe by setting a 'fallbackHandler'.
* This 'fallbackHandler' adds an implementation of 'validateUserOp' to the Safe.
* Note that the implementation of the 'validateUserOp' method is located in the EIP4337Manager.
* Upon receiving the 'validateUserOp', a Safe with EIP4337Fallback enabled makes a 'delegatecall' to EIP4337Manager.
*/
@@ -31,8 +31,8 @@ contract EIP4337Fallback is DefaultCallbackHandler, IAccount, IERC1271 {
*/
function delegateToManager() internal returns (bytes memory) {
// delegate entire msg.data (including the appended "msg.sender") to the EIP4337Manager
// will work only for GnosisSafe contracts
GnosisSafe safe = GnosisSafe(payable(msg.sender));
// will work only for Safe contracts
Safe safe = Safe(payable(msg.sender));
(bool success, bytes memory ret) = safe.execTransactionFromModuleReturnData(eip4337manager, 0, msg.data, Enum.Operation.DelegateCall);
if (!success) {
assembly {
@@ -73,11 +73,11 @@ contract EIP4337Fallback is DefaultCallbackHandler, IAccount, IERC1271 {
function isValidSignature(
bytes32 _hash,
bytes memory _signature
) external override view returns (bytes4) {
) external override (IERC1271) view returns (bytes4) {
bytes32 hash = _hash.toEthSignedMessageHash();
address recovered = hash.recover(_signature);
GnosisSafe safe = GnosisSafe(payable(address(msg.sender)));
Safe safe = Safe(payable(address(msg.sender)));
// Validate signatures
if (safe.isOwner(recovered)) {

View File

@@ -6,9 +6,9 @@ pragma solidity ^0.8.7;
/* solhint-disable reason-string */
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol";
import "@gnosis.pm/safe-contracts/contracts/base/Executor.sol";
import "@gnosis.pm/safe-contracts/contracts/examples/libraries/GnosisSafeStorage.sol";
import "../../safe-contracts/contracts/Safe.sol";
import "../../safe-contracts/contracts/base/Executor.sol";
import "../../safe-contracts/contracts/examples/libraries/Migrate_1_3_0_to_1_2_0.sol";
import "./EIP4337Fallback.sol";
import "../../interfaces/IAccount.sol";
import "../../interfaces/IEntryPoint.sol";
@@ -24,7 +24,7 @@ import "./Verifiers.sol";
* holds an immutable reference to the EntryPoint
* Inherits GnosisSafe so that it can reference the memory storage
*/
contract EIP4337Manager is IAccount, GnosisSafeStorage, Executor {
contract EIP4337Manager is IAccount, SafeStorage, Executor {
address public immutable eip4337Fallback;
address public immutable entryPoint;
@@ -96,7 +96,7 @@ contract EIP4337Manager is IAccount, GnosisSafeStorage, Executor {
require(threshold == 1, "account: only threshold 1");
bytes calldata ecdsaSignature = verificationData[1:];
if (!ecdsaVerifier.verify(
GnosisSafe(payable(address(this))),
Safe(payable(address(this))),
userOpHash,
ecdsaSignature
)) {
@@ -160,7 +160,7 @@ contract EIP4337Manager is IAccount, GnosisSafeStorage, Executor {
function setup4337Modules(
EIP4337Manager manager //the manager (this contract)
) external {
GnosisSafe safe = GnosisSafe(payable(address(this)));
Safe safe = Safe(payable(address(this)));
require(!safe.isModuleEnabled(manager.entryPoint()), "setup4337Modules: entrypoint already enabled");
require(!safe.isModuleEnabled(manager.eip4337Fallback()), "setup4337Modules: eip4337Fallback already enabled");
safe.enableModule(manager.entryPoint());
@@ -175,7 +175,7 @@ contract EIP4337Manager is IAccount, GnosisSafeStorage, Executor {
* @param newManager the new EIP4337Manager, usually with a new EntryPoint
*/
function replaceEIP4337Manager(address prevModule, EIP4337Manager oldManager, EIP4337Manager newManager) public {
GnosisSafe pThis = GnosisSafe(payable(address(this)));
Safe pThis = Safe(payable(address(this)));
address oldFallback = oldManager.eip4337Fallback();
require(pThis.isModuleEnabled(oldFallback), "replaceEIP4337Manager: oldManager is not active");
pThis.disableModule(oldFallback, oldManager.entryPoint());
@@ -195,7 +195,7 @@ contract EIP4337Manager is IAccount, GnosisSafeStorage, Executor {
* the test is might be incomplete: we check that we reach our validateUserOp and fail on signature.
* we don't test full transaction
*/
function validateEip4337(GnosisSafe safe, EIP4337Manager manager) public {
function validateEip4337(Safe safe, EIP4337Manager manager) public {
// this prevents mistaken replaceEIP4337Manager to disable the module completely.
// minimal signature that pass "recover"
@@ -222,7 +222,7 @@ contract EIP4337Manager is IAccount, GnosisSafeStorage, Executor {
* @return prev prev module, needed by replaceEIP4337Manager
* @return manager the current active EIP4337Manager
*/
function getCurrentEIP4337Manager(GnosisSafe safe) public view returns (address prev, address manager) {
function getCurrentEIP4337Manager(Safe safe) public view returns (address prev, address manager) {
prev = address(SENTINEL_MODULES);
(address[] memory modules,) = safe.getModulesPaginated(SENTINEL_MODULES, 100);
for (uint i = 0; i < modules.length; i++) {

View File

@@ -2,19 +2,19 @@
pragma solidity ^0.8.12;
import "@openzeppelin/contracts/utils/Create2.sol";
import "@gnosis.pm/safe-contracts/contracts/proxies/GnosisSafeProxyFactory.sol";
import "../../safe-contracts/contracts/proxies/SafeProxyFactory.sol";
import "./EIP4337Manager.sol";
/**
* A wrapper factory contract to deploy GnosisSafe as an ERC-4337 account contract.
*/
contract GnosisSafeAccountFactory {
contract SafeAccountFactory {
GnosisSafeProxyFactory public immutable proxyFactory;
SafeProxyFactory public immutable proxyFactory;
address public immutable safeSingleton;
EIP4337Manager public immutable eip4337Manager;
constructor(GnosisSafeProxyFactory _proxyFactory, address _safeSingleton, EIP4337Manager _eip4337Manager) {
constructor(SafeProxyFactory _proxyFactory, address _safeSingleton, EIP4337Manager _eip4337Manager) {
proxyFactory = _proxyFactory;
safeSingleton = _safeSingleton;
eip4337Manager = _eip4337Manager;
@@ -39,7 +39,7 @@ contract GnosisSafeAccountFactory {
bytes memory setup4337Modules = abi.encodeCall(
EIP4337Manager.setup4337Modules, (eip4337Manager));
return abi.encodeCall(GnosisSafe.setup, (
return abi.encodeCall(Safe.setup, (
owners, threshold,
address (eip4337Manager), setup4337Modules,
eip4337fallback,

View File

@@ -1,26 +1,26 @@
//SPDX-License-Identifier: GPL
pragma solidity ^0.8.15;
import "@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol";
import "@gnosis.pm/safe-contracts/contracts/examples/libraries/GnosisSafeStorage.sol";
import "../../safe-contracts/contracts/Safe.sol";
import "../../safe-contracts/contracts/examples/libraries/Migrate_1_3_0_to_1_2_0.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "./EIP4337Manager.sol";
interface IVerifier {
function verify(
GnosisSafe safe,
Safe safe,
bytes32 hash,
bytes calldata verificationData
) external view returns (bool);
}
contract ECDSAVerifier is IVerifier, GnosisSafeStorage {
contract ECDSAVerifier is IVerifier, SafeStorage {
using ECDSA for bytes32;
function verify(
GnosisSafe safe,
Safe safe,
bytes32 hash,
bytes calldata ecdsaSignature
) public view returns (bool) {
@@ -30,7 +30,7 @@ contract ECDSAVerifier is IVerifier, GnosisSafeStorage {
}
}
contract BLSGroupVerifier is IVerifier, GnosisSafeStorage {
contract BLSGroupVerifier is IVerifier, SafeStorage {
uint8 public constant BLS_KEY_LEN = 4;
uint256[BLS_KEY_LEN][] public groupMembers;
@@ -43,7 +43,7 @@ contract BLSGroupVerifier is IVerifier, GnosisSafeStorage {
}
function verify(
GnosisSafe safe,
Safe safe,
bytes32 hash,
bytes calldata ecdsaSignature
) public pure returns (bool) {

File diff suppressed because one or more lines are too long

View File

@@ -38,6 +38,12 @@ const optimizedComilerSettings = {
const config: HardhatUserConfig = {
solidity: {
compilers: [{
version: '0.7.6',
settings: {
optimizer: { enabled: true, runs: 1000000 }
}
},
{
version: '0.8.15',
settings: {
optimizer: { enabled: true, runs: 1000000 }

View File

@@ -52,7 +52,7 @@
"typechain": "^8.1.0"
},
"dependencies": {
"@gnosis.pm/safe-contracts": "^1.3.0",
"@gnosis.pm/safe-singleton-factory": "^1.0.3",
"@nomiclabs/hardhat-etherscan": "^2.1.6",
"@openzeppelin/contracts": "^4.2.0",
"@thehubbleproject/bls": "^0.5.1",

View File

@@ -7,12 +7,12 @@ import {
EIP4337Manager__factory,
EntryPoint,
EntryPoint__factory,
GnosisSafe,
GnosisSafeAccountFactory,
GnosisSafeAccountFactory__factory,
GnosisSafeProxy,
GnosisSafeProxyFactory__factory,
GnosisSafe__factory,
Safe,
SafeAccountFactory,
SafeAccountFactory__factory,
SafeProxy,
SafeProxyFactory__factory,
Safe__factory,
TestCounter,
TestCounter__factory
} from '../typechain'
@@ -33,17 +33,17 @@ describe.only('Gnosis Proxy', function () {
this.timeout(30000)
let ethersSigner: Signer
let safeSingleton: GnosisSafe
let safeSingleton: Safe
let owner: Signer
let ownerAddress: string
let proxy: GnosisSafeProxy
let proxy: SafeProxy
let manager: EIP4337Manager
let entryPoint: EntryPoint
let counter: TestCounter
let proxySafe: GnosisSafe
let proxySafe: Safe
let safe_execTxCallData: string
let accountFactory: GnosisSafeAccountFactory
let accountFactory: SafeAccountFactory
before('before', async function () {
// EIP4337Manager fails to compile with solc-coverage
@@ -55,16 +55,16 @@ describe.only('Gnosis Proxy', function () {
ethersSigner = provider.getSigner()
// standard safe singleton contract (implementation)
safeSingleton = await new GnosisSafe__factory(ethersSigner).deploy()
safeSingleton = await new Safe__factory(ethersSigner).deploy()
// standard safe proxy factory
const proxyFactory = await new GnosisSafeProxyFactory__factory(ethersSigner).deploy()
const proxyFactory = await new SafeProxyFactory__factory(ethersSigner).deploy()
entryPoint = await deployEntryPoint()
manager = await new EIP4337Manager__factory(ethersSigner).deploy(entryPoint.address)
owner = createAccountOwner()
ownerAddress = await owner.getAddress()
counter = await new TestCounter__factory(ethersSigner).deploy()
accountFactory = await new GnosisSafeAccountFactory__factory(ethersSigner)
accountFactory = await new SafeAccountFactory__factory(ethersSigner)
.deploy(proxyFactory.address, safeSingleton.address, manager.address)
await accountFactory.createAccount(ownerAddress, 0)
@@ -74,7 +74,7 @@ describe.only('Gnosis Proxy', function () {
const addr = ev[0].args.proxy
proxy =
proxySafe = GnosisSafe__factory.connect(addr, owner)
proxySafe = Safe__factory.connect(addr, owner)
await ethersSigner.sendTransaction({
to: proxy.address,

View File

@@ -471,10 +471,10 @@
"@ethersproject/properties" "^5.7.0"
"@ethersproject/strings" "^5.7.0"
"@gnosis.pm/safe-contracts@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-contracts/-/safe-contracts-1.3.0.tgz#316741a7690d8751a1f701538cfc9ec80866eedc"
integrity sha512-1p+1HwGvxGUVzVkFjNzglwHrLNA67U/axP0Ct85FzzH8yhGJb4t9jDjPYocVMzLorDoWAfKicGy1akPY9jXRVw==
"@gnosis.pm/safe-singleton-factory@^1.0.3":
version "1.0.14"
resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-singleton-factory/-/safe-singleton-factory-1.0.14.tgz#42dae9a91fda21b605f94bfe310a7fccc6a4d738"
integrity sha512-xZ26c9uKzpd5Sm8ux0sZHt5QC8n+Q2z1/X5xjPnd8aT5EcKH5t1GgLbAqjrMFmXVIOkiWSc7wi2Bj4XfgtiyaQ==
"@humanwhocodes/config-array@^0.11.8":
version "0.11.8"