-
Notifications
You must be signed in to change notification settings - Fork 4
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
AssetManager Contract #1
Changes from 8 commits
96fc994
96775c0
f5f2b29
a3a232c
6dbf316
7829e02
206fd47
f02de7f
31de63c
89caeb1
f731def
d13252b
b19be50
be2007d
06f151d
284ec06
918ddca
88d8ec1
6c44982
220f637
d419414
1b9ff86
67f54cd
59c7475
bc2c9e8
71ba069
7198fb5
5af1139
636e4c6
7ab226f
4893499
f1fcd34
c0fc296
019fa66
e8791b7
1fa401d
aa0289b
dc73743
722a874
fc603ab
73ddfd9
960998d
a4c15d7
51ad980
c9d85ab
5ea1802
96c8bc1
21ba43f
26cdf9c
a2d8980
539de19
ab6981e
e405015
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1 | ||
ROPSTEN_URL=https://eth-ropsten.alchemyapi.io/v2/<YOUR ALCHEMY KEY> | ||
PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
node_modules | ||
artifacts | ||
cache | ||
coverage |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
module.exports = { | ||
env: { | ||
browser: false, | ||
es2021: true, | ||
mocha: true, | ||
node: true, | ||
}, | ||
plugins: ["@typescript-eslint"], | ||
extends: [ | ||
"standard", | ||
"plugin:prettier/recommended", | ||
"plugin:node/recommended", | ||
], | ||
parser: "@typescript-eslint/parser", | ||
parserOptions: { | ||
ecmaVersion: 12, | ||
}, | ||
rules: { | ||
"node/no-unsupported-features/es-syntax": [ | ||
"error", | ||
{ ignores: ["modules"] }, | ||
], | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
node_modules | ||
.env | ||
coverage | ||
coverage.json | ||
typechain | ||
|
||
#Hardhat files | ||
cache | ||
artifacts | ||
|
||
# Additional files | ||
docs | ||
data |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
hardhat.config.ts | ||
scripts | ||
test |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
node_modules | ||
artifacts | ||
cache | ||
coverage* | ||
gasReporterOutput.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"extends": "solhint:recommended", | ||
"rules": { | ||
"compiler-version": ["error", "^0.8.0"], | ||
"func-visibility": ["warn", { "ignoreConstructors": true }] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# AssetManager | ||
|
||
Smart contract functionality for managing assets for the sms-wallet. | ||
For an overview read the [wiki](https://github.com/polymorpher/sms-wallet/wiki#sms-controlled-mini-wallet). | ||
|
||
## Developers | ||
|
||
Quickstart | ||
``` | ||
git clone https://github.com/polymorpher/sms-wallet | ||
cd chaincode | ||
yarn | ||
yarn test | ||
``` | ||
|
||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
|
||
pragma solidity ^0.8.9; | ||
|
||
// import "hardhat/console.sol"; | ||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; | ||
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; | ||
import "./Enums.sol"; | ||
|
||
contract AssetManager is Ownable { | ||
johnwhitton marked this conversation as resolved.
Show resolved
Hide resolved
johnwhitton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
event DepositSuccesful(address indexed user, uint256 amount, uint256 balance); | ||
event WithdrawalSuccesful(address indexed user, uint256 amount, uint256 balance); | ||
error WithdrawalFailed( | ||
address user, | ||
uint256 amount, | ||
uint256 balance, | ||
string reason | ||
); | ||
/** | ||
* @dev Emitted when the allowance of a `spender` for an `owner` is set by | ||
* a call to {approve}. `value` is the new allowance. | ||
*/ | ||
event Approval(address indexed owner, address indexed spender, uint256 value); | ||
|
||
// event AuthorizationSuccesful(address indexed user, uint256 newLimit, uint256 userBalance); | ||
error AuthorizationFailed( | ||
address depositor, | ||
uint256 userBalance, | ||
uint256 limit, | ||
string reason | ||
); | ||
event SendSuccesful(address indexed from, address indexed to, uint256 amount, uint256 newBalance, uint256 newLimit); | ||
error SendFailed( | ||
address from, | ||
address to, | ||
uint256 amount, | ||
uint256 balance, | ||
uint256 limit, | ||
string reason | ||
); | ||
event TransferSuccesful(uint256 amount, | ||
Enums.TokenType tokenType, | ||
uint256 tokenId, | ||
address tokenAddress, | ||
address indexed from, | ||
address indexed to); | ||
error TransferFailed( | ||
uint256 amount, | ||
Enums.TokenType tokenType, | ||
uint256 tokenId, | ||
address tokenAddress, | ||
address from, | ||
address to, | ||
string reason | ||
); | ||
|
||
address operator; | ||
uint256 globalUserAuthLimit; | ||
mapping(address => uint256) public userBalances; | ||
// mapping(address => uint256) public userAuthorizations; | ||
mapping(address => mapping(address => uint256)) private _allowances; | ||
|
||
constructor(address operator_, uint256 globalUserAuthLimit_) { | ||
setOperator(operator_); | ||
globalUserAuthLimit = globalUserAuthLimit_; | ||
|
||
} | ||
|
||
modifier onlyOperator { | ||
require(msg.sender == operator, "Can only be called by Operator"); | ||
_; | ||
} | ||
|
||
modifier onlyOwnerOrOperator { | ||
bool ownerOperator = ((msg.sender == owner()) || (msg.sender == operator)); | ||
require(ownerOperator, "Can only be called by Owner or Operator"); | ||
_; | ||
} | ||
|
||
/** | ||
* @dev Returns the address of the current operator. | ||
*/ | ||
|
||
function setOperator(address operator_) onlyOwnerOrOperator public { | ||
operator = operator_; | ||
} | ||
|
||
function deposit() public payable { | ||
userBalances[address(msg.sender)] += msg.value; | ||
// update the userBalance | ||
emit DepositSuccesful( | ||
msg.sender, | ||
msg.value, | ||
userBalances[address(msg.sender)] | ||
); | ||
} | ||
|
||
function withdraw(uint256 amount) public payable { | ||
uint256 balance = userBalances[address(msg.sender)]; | ||
// if zero is passed withdraw all funds | ||
if (amount == 0){ amount = balance; } | ||
// check msg.senders balance | ||
if (amount > balance) { | ||
johnwhitton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
revert WithdrawalFailed( | ||
msg.sender, | ||
amount, | ||
balance, | ||
"Insufficient Locked Funds to Withdraw " | ||
); | ||
} | ||
|
||
// withdraw funds from the contract (update userBalance before transfer to protect from reentracy attack) | ||
uint256 newBalance = balance - amount; | ||
userBalances[address(msg.sender)] = newBalance; | ||
payable(msg.sender).transfer(amount); | ||
|
||
// update the userBalance | ||
emit WithdrawalSuccesful( | ||
msg.sender, | ||
amount, | ||
userBalances[address(msg.sender)] | ||
); | ||
} | ||
|
||
function allowance(address owner, address spender) public view returns (uint256) { | ||
return _allowances[owner][spender]; | ||
} | ||
|
||
function approve(address spender, uint256 amount) public returns (bool) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to be access-controlled and respect limits There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this needs access control (anyone can approve amounts for themselves (msg.sender)). |
||
address owner = _msgSender(); | ||
_approve(owner, spender, amount); | ||
return true; | ||
} | ||
function _approve( | ||
johnwhitton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
address owner, | ||
address spender, | ||
uint256 amount | ||
) internal virtual { | ||
require(owner != address(0), "AssetManager: approve from the zero address"); | ||
require(spender != address(0), "AssetManager: approve to the zero address"); | ||
|
||
_allowances[owner][spender] = amount; | ||
emit Approval(owner, spender, amount); | ||
} | ||
|
||
function send(uint256 amount, address from, address to) public onlyOperator() { | ||
uint256 balance = userBalances[from]; | ||
uint256 currentAllowance = allowance(from, to); | ||
if (amount == 0) { | ||
revert SendFailed( | ||
from, | ||
to, | ||
amount, | ||
balance, | ||
currentAllowance, | ||
"Send amount cannot equal 0" | ||
); | ||
} | ||
// check from balance | ||
if (amount > balance) { | ||
johnwhitton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
revert SendFailed( | ||
from, | ||
to, | ||
amount, | ||
balance, | ||
currentAllowance, | ||
"Insufficient Locked Funds to Send " | ||
); | ||
} | ||
// check from balance | ||
if (amount > currentAllowance) { | ||
revert SendFailed( | ||
from, | ||
to, | ||
amount, | ||
balance, | ||
currentAllowance, | ||
"Insufficient approved funds to send " | ||
); | ||
} | ||
// withdraw funds from the contract (update userBalance before transfer to protect from reentracy attack) | ||
uint256 newBalance = balance - amount; | ||
userBalances[address(from)] = newBalance; | ||
|
||
// update the approved amount. | ||
uint256 newLimit = currentAllowance - amount; | ||
approve(to,newLimit); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would have approved the operator's allowance (as msg.sender), not the user's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great catch 🙏 |
||
|
||
payable(to).transfer(amount); | ||
|
||
emit SendSuccesful( | ||
from, | ||
to, | ||
amount, | ||
newBalance, | ||
newLimit | ||
); | ||
} | ||
function transfer( uint256 amount, Enums.TokenType tokenType, uint256 tokenId, address tokenAddress, address from, address to) public onlyOperator { | ||
if ( tokenType == Enums.TokenType.ERC20 ) { | ||
bool success = ERC20(tokenAddress).transferFrom(from, to, amount); | ||
if (success) { | ||
emit TransferSuccesful(amount, tokenType, tokenId, tokenAddress, from, to); | ||
} else { | ||
revert TransferFailed( | ||
amount, | ||
tokenType, | ||
tokenId, | ||
tokenAddress, | ||
from, | ||
to, | ||
"Invalid tokenType " | ||
); | ||
} | ||
} else if ( tokenType == Enums.TokenType.ERC721 ) { | ||
ERC721(tokenAddress).safeTransferFrom(from, to, tokenId); | ||
emit TransferSuccesful(amount, tokenType, tokenId, tokenAddress, from, to); | ||
} else if ( tokenType == Enums.TokenType.ERC1155 ) { | ||
ERC1155(tokenAddress).safeTransferFrom(from, to, tokenId, amount, ""); | ||
emit TransferSuccesful(amount, tokenType, tokenId, tokenAddress, from, to); | ||
} else { | ||
revert TransferFailed( | ||
amount, | ||
tokenType, | ||
tokenId, | ||
tokenAddress, | ||
from, | ||
to, | ||
"Invalid tokenType " | ||
); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
|
||
pragma solidity ^0.8.9; | ||
|
||
library Enums { | ||
enum TokenType{ | ||
ERC20, ERC721, ERC1155, NONE | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a lot of stuff here. I hope we can simplify to just a few later that's absolutely needed for production. Move test-related variables to a separate example file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the unused variables and created a test object.
Could also remove the non Harmony URL's.
For now I've left them in, in case we want to deploy to other networks.