Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features: Gasless transactions. #89

Merged
merged 31 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5fa4a06
Bumps dep versions.
jamesduncombe Oct 12, 2023
aae803d
Removes unused import and enables tests.
jamesduncombe Oct 12, 2023
1596742
Adds structure, tasks and packages.
jamesduncombe Oct 18, 2023
a1fa47f
Unwraps BasePaymaster due to Ownable dep.
jamesduncombe Oct 19, 2023
18f7175
Fixes IERC165 references.
jamesduncombe Oct 23, 2023
e20eb2b
Fixes IERC20 references.
jamesduncombe Oct 24, 2023
7fdbf82
Refactors file locations for BasePaymaster.
jamesduncombe Oct 23, 2023
2beca99
Merges BasePaymaster into PaymasterTopFacet.
jamesduncombe Oct 26, 2023
5fe5563
Refactors the Paymaster.
jamesduncombe Oct 26, 2023
2085694
Adds tests and refactors paymaster.
jamesduncombe Oct 30, 2023
94a2233
Updates the Paymaster tasks.
jamesduncombe Oct 31, 2023
9727713
Adds issuer init param, refactors tests.
jamesduncombe Oct 31, 2023
b362ca4
Tidies contract import paths.
jamesduncombe Feb 5, 2024
a372140
Brings ERC2771Recipient contract into AHasForwader.
jamesduncombe Feb 5, 2024
fefbd7a
Adds AHasContext.
jamesduncombe Feb 5, 2024
ef7f5b2
Makes AHasMembers context aware.
jamesduncombe Feb 5, 2024
8eb88fc
Makes AHasGovernors context aware.
jamesduncombe Mar 5, 2024
dc82112
Updates Paymaster tasks.
jamesduncombe Feb 5, 2024
10c20f2
Updates CI action versions.
jamesduncombe Feb 5, 2024
7eb6a26
Adds MarketplaceForwardableFacet.
jamesduncombe Feb 5, 2024
683c150
Adds context behaviour to Marketplace facets.
jamesduncombe Feb 5, 2024
0bbcac9
Bumps OpenGSN contracts to v3.0.0-beta.10.
jamesduncombe Feb 7, 2024
2e2fd46
Updates Marketplace init facet for forwardable data.
jamesduncombe Mar 5, 2024
5a472c5
Adds Fast forwardable facet.
jamesduncombe Mar 5, 2024
bdaf271
Updates Fast init facet for forwardable data.
jamesduncombe Mar 5, 2024
89f84c3
Updates deps.
jamesduncombe Apr 3, 2024
0ce0684
Fixes LibHelper and Amoy config. Deploys Paymaster functions.
jamesduncombe Apr 3, 2024
74e6cfa
Deploys Paymaster diamond to Amoy.
jamesduncombe Apr 3, 2024
66ec228
Adds gas price adjustments utils function.
jamesduncombe Apr 4, 2024
a0a22de
Refactors LibHelpers.
jamesduncombe Apr 4, 2024
588a177
Formatting changes.
jamesduncombe Apr 4, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/geth-test-deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:

steps:
- name: Checkout the code
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup Nodejs
uses: actions/setup-node@v3
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:

steps:
- name: Checkout the code
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup Nodejs
uses: actions/setup-node@v3
Expand All @@ -28,7 +28,7 @@ jobs:
yarn coverage

- name: Upload coverage report
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
Expand Down
2 changes: 1 addition & 1 deletion contracts/common/AHasAutomatons.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity 0.8.10;

import "../lib/LibAddressSet.sol";
import "../lib/LibPaginate.sol";
import "../common/lib/LibHasAutomatons.sol";
import "./lib/LibHasAutomatons.sol";

/**
* @title The Fast Smart Contract.
Expand Down
41 changes: 41 additions & 0 deletions contracts/common/AHasContext.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

/**
* @title The Context behaviour abstract contract.
* @notice The AHasContext abstract contract is in charge of adding the required
* base implementation of ERC-2771 - see: https://eips.ethereum.org/EIPS/eip-2771
* the key difference is that `isTrustedForwarder` becomes an internal method.
*/
abstract contract AHasContext {
/// @dev Must be implemented by the inheriting contract.
function _isTrustedForwarder(address forwarder) internal view virtual returns (bool);

// The following is copied from:
// https://github.com/opengsn/gsn/blob/v3.0.0-beta.10/packages/contracts/src/ERC2771Recipient.sol
// With a slight change to call `_isTrustedForwarder` instead of `isTrustedForwarder`.

/// @notice Default implemention.
function _msgSender() internal view virtual returns (address ret) {
if (msg.data.length >= 20 && _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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd go with an early return to get rid of the else statement. Possibly invert the condition to get what's in the current else statement to return early.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yep true... honestly I was trying to modify this as little as possible from their implementation but we are already deviating from it so 🤷‍♂️

Original implementation here: https://github.com/opengsn/gsn/blob/v3.0.0-beta.10/packages/contracts/src/ERC2771Recipient.sol

ret = msg.sender;
}
return ret;
}

/// @notice Default implemention.
function _msgData() internal view virtual returns (bytes calldata ret) {

Check warning on line 34 in contracts/common/AHasContext.sol

View check run for this annotation

Codecov / codecov/patch

contracts/common/AHasContext.sol#L34

Added line #L34 was not covered by tests
if (msg.data.length >= 20 && _isTrustedForwarder(msg.sender)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this condition often. Maybe having it in a private function such as isValidTrustedForwarder could help having concise if statements.

This could then be rewritten such as:

return isValidTrustedForwarder() ? msg.data[...] : msg.data;

return msg.data[0:msg.data.length - 20];

Check warning on line 36 in contracts/common/AHasContext.sol

View check run for this annotation

Codecov / codecov/patch

contracts/common/AHasContext.sol#L36

Added line #L36 was not covered by tests
} else {
return msg.data;

Check warning on line 38 in contracts/common/AHasContext.sol

View check run for this annotation

Codecov / codecov/patch

contracts/common/AHasContext.sol#L38

Added line #L38 was not covered by tests
}
}
}
80 changes: 80 additions & 0 deletions contracts/common/AHasForwarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

import "./lib/LibHasForwarder.sol";
import "../interfaces/ICustomErrors.sol";
import "../interfaces/IERC165.sol"; // Interface Support.

import "@opengsn/contracts/src/forwarder/IForwarder.sol";

/**
* @title The Forwarder behaviour abstract contract.
* @notice The AHasForwarder abstract contract is in charge of adding the Forwarder
* functionality to any contract inheriting from it.
*/
abstract contract AHasForwarder {
/// Errors.

/**
* @notice Happens when a function is called by a non forwarder manager.
* @param who is the address that called the function.
*/
error RequiresForwarderManager(address who);

/// Events.

/**
* @notice Emited when a forwarder is set on an implementing contract.
* @param forwarderAddress is the address of the trusted forwarder.
*/
event ForwarderChanged(address forwarderAddress);

// SEE: https://github.com/opengsn/gsn/blob/v3.0.0-beta.10/packages/contracts/src/ERC2771Recipient.sol

/**
* @notice ERC2771Recipient implementation.
* @param _forwarderAddress the forwarder address.
* @return bool if the forwarder is trusted.
*/
function isTrustedForwarder(address _forwarderAddress) public view returns (bool) {
return _forwarderAddress == LibHasForwarder.data().forwarderAddress;
}

/**
* @notice ERC2771Recipient implementation.
* @param _forwarderAddress the forwarder address.
*/
function setTrustedForwarder(address _forwarderAddress) external onlyForwarderManager {
if (!IERC165(_forwarderAddress).supportsInterface(type(IForwarder).interfaceId))
revert ICustomErrors.InterfaceNotSupported("IForwarder");

Check warning on line 49 in contracts/common/AHasForwarder.sol

View check run for this annotation

Codecov / codecov/patch

contracts/common/AHasForwarder.sol#L49

Added line #L49 was not covered by tests

LibHasForwarder.Data storage ds = LibHasForwarder.data();
ds.forwarderAddress = _forwarderAddress;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any tests we want to do before setting a new forwarder? For example check that it implements a particular interface? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, would be good to handle that 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


emit ForwarderChanged(_forwarderAddress);
}

/**
* WARNING: The Forwarder can have a full control over your Recipient. Only trust verified Forwarder.
* @notice Method is not a required method to allow Recipients to trust multiple Forwarders. Not recommended yet.
* @return forwarderAddress The address of the Forwarder contract that is being used.
*/
function getTrustedForwarder() public view virtual returns (address forwarderAddress) {
return LibHasForwarder.data().forwarderAddress;
}

/**
* @notice Checks whether the given address is a forwarder manager or not.
* @dev Must be implemented by the inheriting contract.
* @param who is the address to test.
*/
function isValidForwarderManager(address who) internal view virtual returns (bool);

/// Modifiers.

/// @notice Ensures that a method can only be called by the forwarder manager.
modifier onlyForwarderManager() virtual {
if (!isValidForwarderManager(msg.sender)) revert RequiresForwarderManager(msg.sender);
_;
}
}
14 changes: 11 additions & 3 deletions contracts/common/AHasGovernors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

import "../lib/LibAddressSet.sol";
import "../lib/LibPaginate.sol";
import "../common/lib/LibHasGovernors.sol";
import "../interfaces/ICustomErrors.sol";
import "./lib/LibHasGovernors.sol";

/**
* @title The Fast Smart Contract.
Expand Down Expand Up @@ -33,6 +33,14 @@
*/
event GovernorRemoved(address indexed governor);

/**
* @notice Default implementation - points to `_msgSender()`.
* @dev May be overriden by the inheriting contract.
*/
function _msgSender() internal view virtual returns (address) {
return msg.sender;

Check warning on line 41 in contracts/common/AHasGovernors.sol

View check run for this annotation

Codecov / codecov/patch

contracts/common/AHasGovernors.sol#L40-L41

Added lines #L40 - L41 were not covered by tests
}

/**
* @notice Checks whether the caller is a governor manager or not.
* @dev Must be implemented by the inheriting contract.
Expand Down Expand Up @@ -94,7 +102,7 @@
* @notice Adds a governor to the list of known governors.
* @param who is the address to be added.
*/
function addGovernor(address who) external onlyGovernorManager(msg.sender) onlyValidGovernor(who) {
function addGovernor(address who) external onlyGovernorManager(_msgSender()) onlyValidGovernor(who) {
// Add the governor.
LibHasGovernors.data().governorSet.add(who, false);
// Notify via callback.
Expand All @@ -109,7 +117,7 @@
* @notice Requires that the caller is a governor of this Issuer.
* @notice Emits a `AHasGovernors.GovernorRemoved` event.
*/
function removeGovernor(address governor) external onlyGovernorManager(msg.sender) {
function removeGovernor(address governor) external onlyGovernorManager(_msgSender()) {
// Notify via callback.
onGovernorRemoved(governor);
// Remove governor.
Expand Down
16 changes: 12 additions & 4 deletions contracts/common/AHasMembers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ pragma solidity 0.8.10;

import "../lib/LibAddressSet.sol";
import "../lib/LibPaginate.sol";
import "../common/lib/LibHasMembers.sol";
import "../interfaces/ICustomErrors.sol";
import "./lib/LibHasMembers.sol";

/**
* @title The Fast Smart Contract.
* @title The Members behaviour abstract contract.
* @notice The Fast Members abstract contract is in charge of keeping track of automaton accounts.
*/
abstract contract AHasMembers {
Expand All @@ -33,6 +33,14 @@ abstract contract AHasMembers {
*/
event MemberRemoved(address indexed member);

/**
* @notice Default implementation - points to `msg.sender`.
* @dev May be overriden by the inheriting contract.
*/
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}

/**
* @notice Checks whether the given address is a members manager or not.
* @dev Must be implemented by the inheriting contract.
Expand Down Expand Up @@ -94,7 +102,7 @@ abstract contract AHasMembers {
* @notice Adds a member to the list of known members.
* @param who is the address to be added.
*/
function addMember(address who) external onlyMemberManager(msg.sender) onlyValidMember(who) {
function addMember(address who) external onlyMemberManager(_msgSender()) onlyValidMember(who) {
// Add the member.
LibHasMembers.data().memberSet.add(who, false);
// Notify via callback.
Expand All @@ -109,7 +117,7 @@ abstract contract AHasMembers {
* @notice Requires that the caller is a member of this Issuer.
* @notice Emits a `AHasMembers.MemberRemoved` event.
*/
function removeMember(address member) external onlyMemberManager(msg.sender) {
function removeMember(address member) external onlyMemberManager(_msgSender()) {
// Notify via callback.
onMemberRemoved(member);
// Remove member.
Expand Down
22 changes: 22 additions & 0 deletions contracts/common/lib/LibHasForwarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

library LibHasForwarder {
// The current version of the storage.
uint16 internal constant STORAGE_VERSION = 1;
// This is `keccak256('HasForwarder.storage.Main')`.
bytes32 internal constant STORAGE_SLOT = 0xa9930c2ffa1b605b0243ba36b3020146bcba5a29c05a711f5ca7c705a8e851ca;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct.


struct Data {
/// @notice The latest intializer version that was called.
uint16 version;
/// @notice This is where we store the trusted forwarder address.
address forwarderAddress;
}

function data() internal pure returns (Data storage s) {
assembly {
s.slot := STORAGE_SLOT
}
}
}
14 changes: 13 additions & 1 deletion contracts/fast/FastAccessFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity 0.8.10;

import "../lib/LibAddressSet.sol";
import "../lib/LibPaginate.sol";
import "../common/AHasContext.sol";
import "../common/AHasMembers.sol";
import "../common/AHasGovernors.sol";
import "../marketplace/MarketplaceAccessFacet.sol";
Expand All @@ -19,7 +20,7 @@ import "./FastAutomatonsFacet.sol";
* @notice The FAST Access facet is the source of truth when it comes to
* permissioning and ACLs within a given FAST.
*/
contract FastAccessFacet is AFastFacet, AHasGovernors, AHasMembers {
contract FastAccessFacet is AFastFacet, AHasGovernors, AHasMembers, AHasContext {
using LibAddressSet for LibAddressSet.Data;
/// Structs.

Expand All @@ -35,6 +36,17 @@ contract FastAccessFacet is AFastFacet, AHasGovernors, AHasMembers {
bool isMember;
}

/// AHasContext implementation.

function _isTrustedForwarder(address forwarder) internal view override(AHasContext) returns (bool) {
return AHasForwarder(address(this)).isTrustedForwarder(forwarder);
}

// Override base classes to use the AHasContext implementation.
function _msgSender() internal view override(AHasMembers, AHasGovernors, AHasContext) returns (address) {
return AHasContext._msgSender();
}

/// AHasGovernors implementation.

function isGovernorsManager(address who) internal view override(AHasGovernors) returns (bool) {
Expand Down
18 changes: 18 additions & 0 deletions contracts/fast/FastForwardableFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

import "../common/AHasForwarder.sol";
import "./lib/AFastFacet.sol";

/**
* @title The Fast forwardable contract.
* @notice The Fast Forwardable facet is in charge of "gasless transactions".
*/
contract FastForwardableFacet is AFastFacet, AHasForwarder {
/// AHasForwarder implementation.

// For now the forwarder manager is an issuer.
function isValidForwarderManager(address who) internal view override(AHasForwarder) returns (bool) {
return _isIssuerMember(who);

Check warning on line 16 in contracts/fast/FastForwardableFacet.sol

View check run for this annotation

Codecov / codecov/patch

contracts/fast/FastForwardableFacet.sol#L15-L16

Added lines #L15 - L16 were not covered by tests
}
}
8 changes: 8 additions & 0 deletions contracts/fast/FastInitFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity 0.8.10;
import "../common/lib/LibHasGovernors.sol";
import "../common/lib/LibHasMembers.sol";
import "../common/lib/LibHasAutomatons.sol";
import "../common/lib/LibHasForwarder.sol";
import "../common/AHasGovernors.sol";
import "../common/AHasMembers.sol";
import "../common/AHasAutomatons.sol";
Expand Down Expand Up @@ -113,5 +114,12 @@ contract FastInitFacet is AFastFacet {

// Initialize automatons storage.
LibHasAutomatons.data().version = LibHasAutomatons.STORAGE_VERSION;

// ------------------------------------- //

// Initialize forwarder storage.
LibHasForwarder.Data storage forwarderData = LibHasForwarder.data();
forwarderData.version = LibHasForwarder.STORAGE_VERSION;
forwarderData.forwarderAddress = address(0);
}
}
4 changes: 2 additions & 2 deletions contracts/fast/FastTokenFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ contract FastTokenFacet is AFastFacet, IERC20 {

// This operation can be seen as a regular transfer between holder and reserve. Emit.
emit FastTransfer(msg.sender, holder, address(0), amount, DEAD_TOKENS_RETRIEVAL);
emit Transfer(holder, address(0), amount);
emit IERC20.Transfer(holder, address(0), amount);

// If amount wasn't zero, total supply and reserve balance have changed - emit.
if (amount > 0) FastFrontendFacet(address(this)).emitDetailsChanged();
Expand Down Expand Up @@ -437,7 +437,7 @@ contract FastTokenFacet is AFastFacet, IERC20 {
}

// Emit!
emit Approval(from, spender, amount);
emit IERC20.Approval(from, spender, amount);
}

/**
Expand Down
6 changes: 2 additions & 4 deletions contracts/fast/lib/IFastEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface IFastEvents {
* @param who is the account from which the minting operation originated.
*/
event Minted(uint256 indexed amount, string ref, address indexed who);

/**
* @notice Emited whenever an burning happens in a FAST.
* @param amount is the amount of tokens that have been burnt.
Expand All @@ -30,12 +31,9 @@ interface IFastEvents {

/// Transfer and ERC20 stuff.

/// @notice See `ERC20.Transfer`.
event Transfer(address indexed from, address indexed to, uint256 value);
/// @notice See `ERC20.Approval`.
event Approval(address indexed owner, address indexed spender, uint256 value);
/// @notice See `ERC20.Disapproval`.
event Disapproval(address indexed owner, address indexed spender, uint256 value);

/**
* @notice As we augmented the ERC20 standard with a few concepts, we emit our custom events
* in addition to the ERC20 ones.
Expand Down
1 change: 1 addition & 0 deletions contracts/interfaces/ICustomErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface ICustomErrors {
error DuplicateEntry();
error InconsistentParameter(string param);
error InsufficientFunds(uint256 amount);
error InterfaceNotSupported(string);
error InternalMethod();
error InvalidCrowdfundBasisPointsFee(uint32 fee);
error InvalidPhase();
Expand Down
Loading
Loading