Skip to content

Commit

Permalink
receive() => proxy, proxy=>BiconomyMSAProxy
Browse files Browse the repository at this point in the history
  • Loading branch information
Filipp Makarov authored and Filipp Makarov committed Dec 15, 2023
1 parent c81cc51 commit ce1b350
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
pragma solidity ^0.8.20;

/**
* @title Proxy // This is the user's Smart Account
* @title BiconomyMSAProxy // This is the user's Smart Account
* @notice Basic proxy that delegates all calls to a fixed implementation contract.
* @dev Implementation address is stored in the slot defined by the Proxy's address
*/
contract Proxy {
contract BiconomyMSAProxy {
constructor(address _implementation) {
require(
_implementation != address(0),
Expand All @@ -17,6 +17,11 @@ contract Proxy {
}
}

receive() external payable {
// leaving it blank for the compatibility reasons,
// as some protocols send value with exactly 2300 gas
}

fallback() external payable {
address target;
assembly {
Expand All @@ -33,4 +38,5 @@ contract Proxy {
}
}
}

}
9 changes: 3 additions & 6 deletions contracts/smart-account/SmartAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,11 @@ contract SmartAccount is
}

/**
* @dev This function is a special fallback function that is triggered when the contract receives Ether.
* It logs an event indicating the amount of Ether received and the sender's address.
* @notice This function is marked as external and payable, meaning it can be called from external
* sources and accepts Ether as payment.
* Left for v1 compatibility reasons
* Otherwise SAs upgraded from v1 won't be able to receive native tokens
* As v1 Proxy doesn't have receive()
*/
receive() external payable {
if (address(this) == SELF) revert DelegateCallsOnly();
emit SmartAccountReceivedNativeToken(msg.sender, msg.value);
}

/// @inheritdoc ISmartAccount
Expand Down
10 changes: 5 additions & 5 deletions contracts/smart-account/factory/SmartAccountFactory.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "../Proxy.sol";
import "../BiconomyMSAProxy.sol";
import "../BaseSmartAccount.sol";
import {DefaultCallbackHandler} from "../handler/DefaultCallbackHandler.sol";
import {Stakeable} from "../common/Stakeable.sol";
Expand Down Expand Up @@ -41,7 +41,7 @@ contract SmartAccountFactory is Stakeable, ISmartAccountFactory {
moduleSetupData
);
bytes memory code = abi.encodePacked(
type(Proxy).creationCode,
type(BiconomyMSAProxy).creationCode,
uint256(uint160(basicImplementation))
);
bytes32 salt = keccak256(
Expand Down Expand Up @@ -69,7 +69,7 @@ contract SmartAccountFactory is Stakeable, ISmartAccountFactory {
);

bytes memory deploymentData = abi.encodePacked(
type(Proxy).creationCode,
type(BiconomyMSAProxy).creationCode,
uint256(uint160(basicImplementation))
);

Expand Down Expand Up @@ -111,7 +111,7 @@ contract SmartAccountFactory is Stakeable, ISmartAccountFactory {
bytes calldata moduleSetupData
) public override returns (address proxy) {
bytes memory deploymentData = abi.encodePacked(
type(Proxy).creationCode,
type(BiconomyMSAProxy).creationCode,
uint256(uint160(basicImplementation))
);

Expand Down Expand Up @@ -152,7 +152,7 @@ contract SmartAccountFactory is Stakeable, ISmartAccountFactory {

/// @inheritdoc ISmartAccountFactory
function accountCreationCode() public pure override returns (bytes memory) {
return type(Proxy).creationCode;
return type(BiconomyMSAProxy).creationCode;
}

/**
Expand Down
11 changes: 1 addition & 10 deletions contracts/smart-account/interfaces/ISmartAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,8 @@ interface ISmartAccount is IBaseSmartAccount, IModuleManager {
address indexed oldImplementation,
address indexed newImplementation
);
event SmartAccountReceivedNativeToken(
address indexed sender,
uint256 indexed value
);

// Error
// Errors

/**
* @notice Throws if zero address has been provided as Entry Point address
Expand Down Expand Up @@ -105,11 +101,6 @@ interface ISmartAccount is IBaseSmartAccount, IModuleManager {
*/
error WrongValidationModule(address moduleAddressProvided);

/**
* @notice Thrown when the function that must be called only via delegatecall is called directly
*/
error DelegateCallsOnly();

/**
* @notice Thrown when trying to use address of the Smart Account as an owner for itself
*/
Expand Down
18 changes: 18 additions & 0 deletions contracts/smart-account/test/mocks/MockEthSender.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;

contract MockEthSender {

receive() external payable {
//
}

function send(address to, uint256 amount, uint256 gasStipend) external payable {
bool success;
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
if(!success)
revert("Can not send eth");
}
}
8 changes: 4 additions & 4 deletions test/Proxy.specs.ts → test/BiconomyMSAProxy.specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { expect } from "chai";
import { ethers, waffle } from "hardhat";
import { AddressZero } from "@ethersproject/constants";

describe("Proxy ", async () => {
describe("BiconomyMSAProxy ", async () => {
const [randomAddress] = waffle.provider.getWallets();

describe("constructor", async () => {
it("should revert with invalid implementation address", async () => {
const Proxy = await ethers.getContractFactory(
"contracts/smart-account/Proxy.sol:Proxy"
"contracts/smart-account/BiconomyMSAProxy.sol:BiconomyMSAProxy"
);
await expect(Proxy.deploy(AddressZero)).to.be.revertedWith(
"Invalid implementation address"
Expand All @@ -18,7 +18,7 @@ describe("Proxy ", async () => {
it("should store implementation at the slot with address encoded as proxy address", async () => {
const implementationAddress = randomAddress.address;
const Proxy = await ethers.getContractFactory(
"contracts/smart-account/Proxy.sol:Proxy"
"contracts/smart-account/BiconomyMSAProxy.sol:BiconomyMSAProxy"
);
const proxy = await Proxy.deploy(implementationAddress);
await proxy.deployed();
Expand All @@ -35,7 +35,7 @@ describe("Proxy ", async () => {
it("reverts when trying to delegatecall to the EOA implementation", async () => {
const implementationAddress = randomAddress.address;
const Proxy = await ethers.getContractFactory(
"contracts/smart-account/Proxy.sol:Proxy"
"contracts/smart-account/BiconomyMSAProxy.sol:BiconomyMSAProxy"
);
let proxy = await Proxy.deploy(implementationAddress);
await proxy.deployed();
Expand Down
24 changes: 24 additions & 0 deletions test/foundry/smart-account/SA.Basics.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {SATestBase} from "../base/SATestBase.sol";
import {SmartAccount} from "sa/SmartAccount.sol";
import {EcdsaOwnershipRegistryModule} from "modules/EcdsaOwnershipRegistryModule.sol";
import {UserOperation} from "aa-core/EntryPoint.sol";
import {MockEthSender} from "sa/test/mocks/MockEthSender.sol";

contract Test {
event Log(string message);
Expand Down Expand Up @@ -97,4 +98,27 @@ contract SABasicsTest is SATestBase {
vm.breakpoint("a");
entryPoint.handleOps(arraifyOps(op), owner.addr);
}

function testReceiveEtherWithGasLimit() external {
// Deploy Smart Account with default module
uint256 smartAccountDeploymentIndex = 0;
bytes memory moduleSetupData = getEcdsaOwnershipRegistryModuleSetupData(
alice.addr
);
SmartAccount sa = getSmartAccountWithModule(
address(ecdsaOwnershipRegistryModule),
moduleSetupData,
smartAccountDeploymentIndex,
"aliceSA"
);

MockEthSender mockEthSender = new MockEthSender();
vm.deal(address(mockEthSender), 100 ether);

uint256 gasStipend = 0;
// by some reason it passes even if there's extra gas consumed by receive()
// same doesn't pass with Hardhat
// see SA.Basics.specs.ts for reference
mockEthSender.send(address(sa), 1 ether, gasStipend);
}
}
25 changes: 25 additions & 0 deletions test/smart-account/SA.Basics.specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,29 @@ describe("Modular Smart Account Basics: ", async () => {
);
expect(returnedValue).to.be.equal(eip1271MagicValue);
});

it("Can receive native token with 2300 gas", async () => {
const { userSA, ecdsaModule, entryPoint } = await setupTests();

const amountToSend = ethers.utils.parseEther("0.1");

//deploy MockEthSender

Check failure on line 114 in test/smart-account/SA.Basics.specs.ts

View workflow job for this annotation

GitHub Actions / Lint sources (18.x)

Expected space or tab after '//' in comment
const mockEthSender = await (
await ethers.getContractFactory("MockEthSender")
).deploy();

// send funds to it
await deployer.sendTransaction({
to: mockEthSender.address,
value: ethers.utils.parseEther("10"),
});

const provider = entryPoint?.provider;
const userSABalanceBefore = await provider.getBalance(userSA.address);

const gasStipend = 0;
await mockEthSender.send(userSA.address, amountToSend, gasStipend);
expect(await provider.getBalance(userSA.address)).to.equal(userSABalanceBefore.add(amountToSend));

Check failure on line 130 in test/smart-account/SA.Basics.specs.ts

View workflow job for this annotation

GitHub Actions / Lint sources (18.x)

Replace `userSABalanceBefore.add(amountToSend)` with `⏎······userSABalanceBefore.add(amountToSend)⏎····`
});

Check failure on line 131 in test/smart-account/SA.Basics.specs.ts

View workflow job for this annotation

GitHub Actions / Lint sources (18.x)

Delete `⏎`

});

0 comments on commit ce1b350

Please sign in to comment.