Skip to content

Instantly share code, notes, and snippets.

@drortirosh
Created June 21, 2020 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drortirosh/a1d984208f215638ed87436bdb47892b to your computer and use it in GitHub Desktop.
Save drortirosh/a1d984208f215638ed87436bdb47892b to your computer and use it in GitHub Desktop.
Sample on funding proxy account without eth.
//SPDX-License-Identifier: MIT
pragma solidity ^0.6.9;
//helper: decode error message
// (NOT efficient: using calldata, which means string copies..)
library LibError {
function getError(string calldata txt, bytes calldata ret) external pure returns(string memory) {
if ( ret.length<36) return "";
return string(abi.encodePacked(txt, abi.decode(ret[4:], (string))));
}
}
//sample funder contract. can only fund its owner.
contract Funder {
address owner;
constructor(address _owner) public payable {
owner=_owner;
}
receive() external payable {}
//owner ask to get funded.
// called from a proxy object
// (real funder will have approval to extract tokens from owner.)
function fundMe(uint value) external {
require( msg.sender == owner, "funder: not owner");
msg.sender.transfer(value);
}
//Helper method, to be called by Proxy, with "delegatecall"
// (has to be somewhere, not neccesarily inside the funder)
// performs a batch of 2 operations:
// 1. proxy calls loaner.fundMe
// 2. proxy calls target method
function execWithFunding(Funder funder, uint value, address target, bytes memory func) public {
require( value>0, "fund without value");
bool success; bytes memory ret;
//we're in delegatecall: actual sender is the proxy:
funder.fundMe(value);
// (success, ret) = address(loaner).call(abi.encodeWithSelector(Loaner.fundMe.selector, value));
// require(success, string(abi.encodePacked("funding failed: ", LibError.getError(ret))));
(success, ret) = target.call{value:value}(func);
require(success, LibError.getError("exec with fundiing failed: ",ret));
}
}
contract Proxy {
address owner;
constructor(address _owner) public {
owner=_owner;
}
receive() external payable {}
function execute(bool delegate, address target, uint value, bytes calldata func) external {
require( msg.sender==owner, "exec: not owner");
require( address(this).balance >= value, "exec: not enough balance");
bool success; bytes memory ret;
if ( !delegate ) {
(success, ret)=target.call{value:value}(func);
} else {
(success, ret)=target.delegatecall(func);
}
if ( !success) {
require(success, LibError.getError("exec failed: ",ret));
}
}
}
contract Target {
event CalledFrom(address sender, uint value);
function mustHaveValue() external payable {
require( msg.value!= 0 , "mustHaveValue: requires value");
emit CalledFrom(msg.sender, msg.value);
}
receive() external payable{}
}
contract ATest {
address target;
Proxy proxy;
address payable funder;
constructor() public payable {
//NOTE: Remix bug: sometimes it ignores the value passed to constructor...
require( msg.value>0, " === test must be given some eth value");
uint v=msg.value;
proxy = new Proxy(address(this));
target = address(new Target());
funder = address(new Funder(address(proxy)));
funder.transfer(v);
//this fails on "not enough balance" (proxy doesn't have eth)
// proxy.execute(false, target, v, abi.encodeWithSelector(Target.mustHaveValue.selector));
//this fails on "something: requires value"
// proxy.execute(false, target, 0, abi.encodeWithSelector(Target.mustHaveValue.selector));
//this funds the proxy with value and call mustHaveValue() with that value
proxy.execute(true, funder, 0,
abi.encodeWithSelector(Funder.execWithFunding.selector,
funder, v,
target, abi.encodeWithSelector(Target.mustHaveValue.selector)
));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment