Skip to content

Commit

Permalink
fix: fix contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
fedealconada committed Jan 23, 2024
1 parent 0cff01c commit 81c1b99
Show file tree
Hide file tree
Showing 14 changed files with 510 additions and 473 deletions.
123 changes: 65 additions & 58 deletions src/apps/KintoAppRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pragma solidity ^0.8.18;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

Expand All @@ -25,24 +24,31 @@ contract KintoAppRegistry is
IKintoAppRegistry
{
/* ============ Constants ============ */

uint256 public constant override RATE_LIMIT_PERIOD = 1 minutes;
uint256 public constant override RATE_LIMIT_THRESHOLD = 10;
uint256 public constant override GAS_LIMIT_PERIOD = 30 days;
uint256 public constant override GAS_LIMIT_THRESHOLD = 0.01 ether;

/* ============ State Variables ============ */

IKintoWalletFactory public immutable override walletFactory;

mapping(address => IKintoAppRegistry.Metadata) private _appMetadata;
// other contracts to be sponsored that dont belong in the app

// mapping between an app and all the contracts that it sponsors (that belong to the app)
mapping(address => address) public override childToParentContract; // child => parent (app)

// contracts the app decides to sponsor (that dont belong to the app)
mapping(address => mapping(address => bool)) private _sponsoredContracts;
// Mapping between the app and all the contracts that belong to it
mapping(address => address) public override childToParentContract;
// Mapping between token id and app metadata
mapping(uint256 => address) public override tokenIdToApp;

mapping(uint256 => address) public override tokenIdToApp; // token ID => app metadata

uint256 public override appCount;

/* ============ Events ============ */
event AppCreated(address indexed _app, address _owner, uint256 _timestamp);

event AppRegistered(address indexed _app, address _owner, uint256 _timestamp);
event AppUpdated(address indexed _app, address _owner, uint256 _timestamp);
event AppDSAEnabled(address indexed _app, uint256 _timestamp);

Expand Down Expand Up @@ -112,30 +118,13 @@ contract KintoAppRegistry is
) external override {
require(_appMetadata[parentContract].tokenId == 0, "App already registered");
require(childToParentContract[parentContract] == address(0), "Parent contract already registered as a child");
require(walletFactory.walletTs(parentContract) == 0, "Wallets can not be registered");

appCount++;
_updateMetadata(appCount, _name, parentContract, appContracts, appLimits);
_safeMint(msg.sender, appCount);
emit AppCreated(parentContract, msg.sender, block.timestamp);
}

/**
* @dev Allows the developer to set sponsored contracts
* @param _app The address of the app
* @param _contracts The addresses of the contracts
* @param _flags The flags of the contracts
*/
function setSponsoredContracts(address _app, address[] calldata _contracts, bool[] calldata _flags)
external
override
{
require(_contracts.length == _flags.length, "Invalid input");
require(
_appMetadata[_app].tokenId > 0 && msg.sender == ownerOf(_appMetadata[_app].tokenId),
"Only developer can set sponsored contracts"
);
for (uint256 i = 0; i < _contracts.length; i++) {
_sponsoredContracts[_app][_contracts[i]] = _flags[i];
}
emit AppRegistered(parentContract, msg.sender, block.timestamp);
}

/**
Expand All @@ -154,9 +143,30 @@ contract KintoAppRegistry is
uint256 tokenId = _appMetadata[parentContract].tokenId;
require(msg.sender == ownerOf(tokenId), "Only developer can update metadata");
_updateMetadata(tokenId, _name, parentContract, appContracts, appLimits);

emit AppUpdated(parentContract, msg.sender, block.timestamp);
}

/**
* @dev Allows the developer to set sponsored contracts
* @param _app The address of the app
* @param _contracts The addresses of the contracts
* @param _flags The flags of the contracts
*/
function setSponsoredContracts(address _app, address[] calldata _contracts, bool[] calldata _flags)
external
override
{
require(_contracts.length == _flags.length, "Invalid input");
require(
_appMetadata[_app].tokenId > 0 && msg.sender == ownerOf(_appMetadata[_app].tokenId),
"Only developer can set sponsored contracts"
);
for (uint256 i = 0; i < _contracts.length; i++) {
_sponsoredContracts[_app][_contracts[i]] = _flags[i];
}
}

/**
* @dev Allows the app to request PII data
* @param app The name of the app
Expand All @@ -167,17 +177,30 @@ contract KintoAppRegistry is
emit AppDSAEnabled(app, block.timestamp);
}

/* ============ App Info Fetching ============ */
/**
* @dev Returns whether the contract implements the interface defined by the id
* @param interfaceId id of the interface to be checked.
* @return true if the contract implements the interface defined by the id.
*/
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721Upgradeable, ERC721EnumerableUpgradeable)
returns (bool)
{
return super.supportsInterface(interfaceId);

Check warning on line 191 in src/apps/KintoAppRegistry.sol

View check run for this annotation

Codecov / codecov/patch

src/apps/KintoAppRegistry.sol#L191

Added line #L191 was not covered by tests
}

/* ============ Getters ============ */

/**
* @dev Returns the metadata of the app
* @param _contract The address of the app
* @return The metadata of the app
*/
function getAppMetadata(address _contract) external view override returns (IKintoAppRegistry.Metadata memory) {
address mainContract =
childToParentContract[_contract] != address(0) ? childToParentContract[_contract] : _contract;
return _appMetadata[mainContract];
return
_appMetadata[childToParentContract[_contract] != address(0) ? childToParentContract[_contract] : _contract];
}

/**
Expand All @@ -186,9 +209,8 @@ contract KintoAppRegistry is
* @return The limits of the app
*/
function getContractLimits(address _contract) external view override returns (uint256[4] memory) {
address mainContract =
childToParentContract[_contract] != address(0) ? childToParentContract[_contract] : _contract;
IKintoAppRegistry.Metadata memory metadata = _appMetadata[mainContract];
IKintoAppRegistry.Metadata memory metadata =
_appMetadata[childToParentContract[_contract] != address(0) ? childToParentContract[_contract] : _contract];
return [
metadata.rateLimitPeriod != 0 ? metadata.rateLimitPeriod : RATE_LIMIT_PERIOD,
metadata.rateLimitNumber != 0 ? metadata.rateLimitNumber : RATE_LIMIT_THRESHOLD,
Expand All @@ -203,23 +225,23 @@ contract KintoAppRegistry is
* @param _contract The address of the contract
* @return bool true or false
*/
function isContractSponsored(address _app, address _contract) external view override returns (bool) {
function isSponsored(address _app, address _contract) external view override returns (bool) {
return _contract == _app || childToParentContract[_contract] == _app || _sponsoredContracts[_app][_contract];
}

/**
* @dev Returns the sponsoring contract
* @dev Returns the sponsoring contract for a given contract (aka parent contract)
* @param _contract The address of the contract
* @return The address of the contract that sponsors the contract
*/
function getSponsor(address _contract) external view override returns (address) {
if (childToParentContract[_contract] != address(0)) {
return childToParentContract[_contract];
}
address sponsor = childToParentContract[_contract];
if (sponsor != address(0)) return sponsor;
return _contract;
}

/* =========== App metadata params =========== */
/* =========== Internal methods =========== */

function _updateMetadata(
uint256 tokenId,
string calldata _name,
Expand All @@ -236,15 +258,16 @@ contract KintoAppRegistry is
gasLimitPeriod: appLimits[2],
gasLimitCost: appLimits[3]
});

tokenIdToApp[tokenId] = parentContract;
_appMetadata[parentContract] = metadata;

for (uint256 i = 0; i < appContracts.length; i++) {
require(walletFactory.walletTs(appContracts[i]) == 0, "Wallets can not be registered");
childToParentContract[appContracts[i]] = parentContract;
}
}

/* ============ Disable token transfers ============ */

/**
* @dev Hook that is called before any token transfer. Allow only mints and burns, no transfers.
* @param from source address
Expand All @@ -259,20 +282,4 @@ contract KintoAppRegistry is
require((from == address(0) && to != address(0)), "Only mint transfers are allowed");
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
}

/* ============ Interface ============ */

/**
* @dev Returns whether the contract implements the interface defined by the id
* @param interfaceId id of the interface to be checked.
* @return true if the contract implements the interface defined by the id.
*/
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721Upgradeable, ERC721EnumerableUpgradeable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
2 changes: 1 addition & 1 deletion src/interfaces/IKintoAppRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ interface IKintoAppRegistry {

function getSponsor(address _contract) external view returns (address);

function isContractSponsored(address _app, address _contract) external view returns (bool);
function isSponsored(address _app, address _contract) external view returns (bool);

function walletFactory() external view returns (IKintoWalletFactory);

Expand Down
4 changes: 3 additions & 1 deletion src/interfaces/IKintoWallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface IKintoWallet {

function startRecovery() external;

function finishRecovery(address[] calldata newSigners) external;
function completeRecovery(address[] calldata newSigners) external;

function cancelRecovery() external;

Expand Down Expand Up @@ -72,4 +72,6 @@ interface IKintoWallet {
function ALL_SIGNERS() external view returns (uint8);

function RECOVERY_TIME() external view returns (uint256);

function WALLET_TARGET_LIMIT() external view returns (uint256);
}
57 changes: 34 additions & 23 deletions src/paymasters/SponsorPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,10 @@ contract SponsorPaymaster is Initializable, BasePaymaster, UUPSUpgradeable, Reen
uint256 ethMaxCost = (maxCost + COST_OF_POST * gasPriceUserOp);
require(ethMaxCost <= MAX_COST_OF_USEROP, "SP: gas too high for user op");

// get target contract from calldata
address targetAccount = _getSponsor(userOp.sender, userOp.callData);

require(unlockBlock[targetAccount] == 0, "SP: deposit not locked");
require(balances[targetAccount] >= ethMaxCost, "SP: deposit too low");

return (abi.encode(targetAccount, userOp.sender, userOp.maxFeePerGas, userOp.maxPriorityFeePerGas), 0);
address sponsor = appRegistry.getSponsor(_decodeCallData(userOp.callData));
require(unlockBlock[sponsor] == 0, "SP: deposit not locked");
require(balances[sponsor] >= ethMaxCost, "SP: deposit too low");
return (abi.encode(sponsor, userOp.sender, userOp.maxFeePerGas, userOp.maxPriorityFeePerGas), 0);
}

/**
Expand Down Expand Up @@ -280,30 +277,44 @@ contract SponsorPaymaster is Initializable, BasePaymaster, UUPSUpgradeable, Reen
);
}

/// @dev extracts the target contract from the calldata and calls the app registry to get the sponsor
function _getSponsor(address sender, bytes calldata callData) internal view returns (address sponsor) {
bytes4 selector = bytes4(callData[:4]); // function selector
if (selector == IKintoWallet.executeBatch.selector) {
// decode callData for executeBatch
(address[] memory targets,,) = abi.decode(callData[4:], (address[], uint256[], bytes[]));
sponsor = appRegistry.getSponsor(targets[targets.length - 1]);

// last contract must be a contract app
for (uint256 i = 0; i < targets.length - 1; i++) {
if (!appRegistry.isContractSponsored(sponsor, targets[i]) && targets[i] != sender) {
revert("SP: executeBatch targets must be sponsored by the contract or be the sender wallet");
}
}
} else if (selector == IKintoWallet.execute.selector) {
// decode callData for execute
/// Return app's sponsor from the registry
/// @return sponsor - the sponsor address
/// @dev reverts if neither execute nor executeBatch
/// @dev ensures all targets are sponsored by the same app if executeBatch (todo: decouple this)
function _getSponsor(bytes calldata callData) internal view returns (address sponsor) {
bytes4 selector = bytes4(callData[:4]);

Check warning on line 285 in src/paymasters/SponsorPaymaster.sol

View check run for this annotation

Codecov / codecov/patch

src/paymasters/SponsorPaymaster.sol#L285

Added line #L285 was not covered by tests
if (selector == IKintoWallet.execute.selector) {
(address target,,) = abi.decode(callData[4:], (address, uint256, bytes));
sponsor = appRegistry.getSponsor(target);
} else if (selector == IKintoWallet.executeBatch.selector) {
// last target is the sponsor
(address[] memory targets,,) = abi.decode(callData[4:], (address[], uint256[], bytes[]));
sponsor = appRegistry.getSponsor(targets[targets.length - 1]);

Check warning on line 292 in src/paymasters/SponsorPaymaster.sol

View check run for this annotation

Codecov / codecov/patch

src/paymasters/SponsorPaymaster.sol#L291-L292

Added lines #L291 - L292 were not covered by tests
} else {
// handle unknown function or error
revert("SP: Unknown function selector");
}
}

// @notice extracts `target` contract from callData
// @dev the last op on a batch MUST always be a contract whose sponsor is the one we want to
// bear with the gas cost of all ops
// @dev this is very similar to KintoWallet._decodeCallData, consider unifying
function _decodeCallData(bytes calldata callData) private pure returns (address target) {
bytes4 selector = bytes4(callData[:4]); // extract the function selector from the callData

if (selector == IKintoWallet.executeBatch.selector) {
// decode executeBatch callData
(address[] memory targets,,) = abi.decode(callData[4:], (address[], uint256[], bytes[]));

Check warning on line 308 in src/paymasters/SponsorPaymaster.sol

View check run for this annotation

Codecov / codecov/patch

src/paymasters/SponsorPaymaster.sol#L308

Added line #L308 was not covered by tests
if (targets.length == 0) return address(0);

// target is the last element of the batch
target = targets[targets.length - 1];

Check warning on line 312 in src/paymasters/SponsorPaymaster.sol

View check run for this annotation

Codecov / codecov/patch

src/paymasters/SponsorPaymaster.sol#L312

Added line #L312 was not covered by tests
} else if (selector == IKintoWallet.execute.selector) {
(target,,) = abi.decode(callData[4:], (address, uint256, bytes)); // decode execute callData
}
}

function _min(uint256 a, uint256 b) private pure returns (uint256) {
return a < b ? a : b;
}
Expand Down
Loading

0 comments on commit 81c1b99

Please sign in to comment.